diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..e56852937 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. MacOS Big Sur, Windows 11] + - Version [e.g. 56.0.4] + + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..5b063201e --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "maven" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "daily" diff --git a/.github/workflows/codacy.yml b/.github/workflows/codacy.yml new file mode 100644 index 000000000..2b8289c07 --- /dev/null +++ b/.github/workflows/codacy.yml @@ -0,0 +1,61 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +# This workflow checks out code, performs a Codacy security scan +# and integrates the results with the +# GitHub Advanced Security code scanning feature. For more information on +# the Codacy security scan action usage and parameters, see +# https://github.com/codacy/codacy-analysis-cli-action. +# For more information on Codacy Analysis CLI in general, see +# https://github.com/codacy/codacy-analysis-cli. + +name: Codacy Security Scan + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '39 16 * * 0' + +permissions: + contents: read + +jobs: + codacy-security-scan: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Codacy Security Scan + runs-on: ubuntu-latest + steps: + # Checkout the repository to the GitHub Actions runner + - name: Checkout code + uses: actions/checkout@v3 + + # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis + - name: Run Codacy Analysis CLI + uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b + with: + # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository + # You can also omit the token and run the tools that support default configurations + project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} + verbose: true + output: results.sarif + format: sarif + # Adjust severity of non-security issues + gh-code-scanning-compat: true + # Force 0 exit code to allow SARIF file generation + # This will handover control about PR rejection to the GitHub side + max-allowed-issues: 2147483647 + + # Upload the SARIF file generated in the previous step + - name: Upload SARIF results file + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: results.sarif diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..5824b8156 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,89 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + schedule: + - cron: '16 10 * * 4' + +jobs: + analyze: + name: Analyze + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + # required for all workflows + security-events: write + + # only required for workflows in private repositories + actions: read + contents: read + + strategy: + fail-fast: false + matrix: + language: [ 'java-kotlin' ] + # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] + # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both + # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + - name: Setup Java JDK + uses: actions/setup-java@v4.0.0 + with: + distribution: 'temurin' + java-version: 17 + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index 097597f26..edcba776c 100644 --- a/.gitignore +++ b/.gitignore @@ -22,4 +22,21 @@ Thumbs.db #swpfiles config.properties~ +*.csr +*.key +#python files +__pycache__/ + +#command line editor working and backup files +*.swp +*.*~ + +# local-proj-repo files +local-proj-repo/classworlds/ +local-proj-repo/com/ +local-proj-repo/junit/ +local-proj-repo/org/ +pom.xml.save +pom.xml.versionsBackup +dependency-reduced-pom.xml diff --git a/.gitmodules b/.gitmodules index df39d7553..e69de29bb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "yoursway-create-dmg"] - path = yoursway-create-dmg - url = git://github.com/andreyvit/yoursway-create-dmg.git diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 000000000..522fa4a0f --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,2 @@ +# Comment line immediately above ownership line is reserved for related gus information. Please be careful while editing. +#ECCN:Open Source diff --git a/README.md b/README.md index ca150b5ac..ab57fe554 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,136 @@ -# Build Data Loader +# Feature requests +Submit your feature request as an idea on [Salesforce IdeaExchange](https://ideas.salesforce.com/s/). Make sure to use "Platform / Data Import & Integration" as the category for your idea. - git clone git@github.com:forcedotcom/dataloader.git - git submodule init - git submodule update - mvn clean package -DskipTests - -The build will include the appropriate eclipse swt jar by detecting the operating system type. If you would like to manually specify the eclipse swt jar, take a look at the pom.xml file to see a full list of available profiles. +# Prerequisites -Note: salesforce.com officially supports dataloader on Windows XP and Windows 7. The other platforms that dataloader can be compiled for are unofficial. +Java Runtime Environment (JRE) is required to install and run Data Loader. Review the installation instructions of the latest release for the required JRE version. -The build will generate a windows installer exe in the target directory when executing the build in a windows environment. If you are packaging the windows installer for distribution, you must use the following command to specify the win32 profile because the installer is packaged with a 32-bit JRE. +# Installing Data Loader +Salesforce officially supports Data Loader for Windows and macOS. All other operating systems are unsupported. The list of supported macOS and Windows versions and CPU architecture for a released version of Data Loader is provided in the [Release Notes](https://github.com/forcedotcom/dataloader/releases) for that release. - mvn clean package -P win32,-win64 -DskipTests - -# Execute Data Loader +[installation instructions for macOS and Windows](https://help.salesforce.com/articleView?id=sf.loader_install_mac.htm). + +Installing on Linux: +- Extract contents of Data Loader zip file +- Rename `install.command` as `install.sh` +- Run the command in a shell terminal: `./install.sh` + +# Running Data Loader in GUI mode -To run the Data Loader GUI, run the command +For running Data Loader on macOS or Windows, follow the [instructions](https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm). - java -jar target/dataloader-xx.0-uber.jar +For running Data Loader on Linux, type the following command in a command shell: + + ./dataloader.sh + + OR -Use the command below to run the Data Loader GUI on Mac + java -jar dataloader-x.y.z.jar - java -XstartOnFirstThread -jar target/dataloader-xx.0-uber.jar +Consult the [documentation](https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm) for the details of how to configure and use Data Loader. -To run data loader for debug +# Running Data Loader in Batch mode +Batch mode is officially supported only on Windows. To run Data Loader in Batch mode on Windows, see [Batch mode for Windows](https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_batchmode_intro.htm). - java -XstartOnFirstThread -jar -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=5005 target/dataloader-xx.0.0-uber.jar +Execute the following command on Mac (Replace `dataloader_console` with `dataloader.sh` on Linux): -To run Data Loader from the command line, use the command: + ./dataloader_console run.mode=batch + +Alternately execute one of the following commands: + + java -jar dataloader-x.y.z.jar run.mode=batch + + OR + + java -jar dataloader-x.y.z.jar salesforce.config.dir= process.name= run.mode=batch + - java -cp target/dataloader-xx.0-uber.jar -Dsalesforce.config.dir=CONFIG_DIR com.salesforce.dataloader.process.ProcessRunner +## Commands to create an encryption key file, encrypt a password, or decrypt a password +See [Batch mode for Windows](https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_batchmode_intro.htm) for the detailed steps to create an encryption key file, encrypt a password, or decrypt a password on Windows. -The command-line version runs with whatever properties you have in your config.properties file, but you can also pass paramters at runtime as arguments to the program. +Batch mode requires specifying an encrypted password in process-conf.xml, config.properties, or as a command line argument. The first step in encrypting a password is to create an encryption key file on Mac or Linux. -For example, the following command sets the operation to insert regardless of what settings are contained in the config.properties file: +Execute the following command to generate an encryption key file on Mac (Replace `dataloader_console` with `dataloader.sh` on Linux): + + ./dataloader_console -k [] run.mode=encrypt + + OR + + java -jar dataloader-x.y.z.jar -k [] run.mode=encrypt + + Execute the following command to encrypt a password on Mac (Replace `dataloader_console` with `dataloader.sh` on Linux): + + ./dataloader_console -e [] run.mode=encrypt + + OR + + java -jar dataloader-x.y.z.jar -e [] run.mode=encrypt - java -cp target/dataloader-xx.0-uber.jar -Dsalesforce.config.dir=CONFIG_DIR com.salesforce.dataloader.process.ProcessRunner process.operation=insert +Execute the following command to decrypt a password on Mac (Replace `dataloader_console` with `dataloader.sh` on Linux): + + ./dataloader_console -d [] run.mode=encrypt + + OR + + java -jar dataloader-x.y.z.jar -d [] run.mode=encrypt + +NOTE: these commands use the default encryption key file `${HOME}/.dataloader/dataloader.key` if an encryption key file is not specified. + +# Reporting an issue +Collect the following information before reaching out to Salesforce Support or reporting an issue on github: +- Data Loader version, desktop operating system type and version, operation being performed, and screenshots of the issue. +- Config files: `config.properties`, `log4j2.properties` or `log-conf.xml`, `process-conf.xml`. +- log file: + - Set the log level to “debug” in Advanced Config dialog (v58 and later). If the log level is not visible in Advanced Settings dialog (v57 or earlier) or if the log level is not changeable in Advanced Settings dialog, set "root" log level to "debug" in `log-conf.xml`. + - Rerun data loader to reproduce the issue. + - Send the log output located in the file shown by “Logging output file” info in the Advanced Settings dialog of their data loader. Logging output file info is shown in the Advanced Settings dialog as of v58. + - If you are using v58 or earlier, the default location of the debug log is `/sdl.log` + - The default tempdir is `%USER%\AppData\Local\Temp` on Windows + - The default tempdir is `${TMPDIR}` on macOS +- Provide a sample csv file containing as few as possible columns and rows to reproduce the issue. +- Provide the following information about your org (it is available in the log file if data loader version > 58.0.0, the log level is set to debug, and the user logs in): + - `Org id`: Setup >> Company Information >> value of Salesforce organization id field + - `instance`: Setup >> Company Information >> value of Instance field + - `User id`: follow the instructions in [this article](https://help.salesforce.com/s/articleView?id=000381643&language=en_US&type=1). + +NOTE: +Remove all personal, business-specific, and all other sensitive information from the files you share (e.g. config files, log files, screenshots, csv files, and others) before reporting an issue, especially on a public forum such as github. + +# Building Data Loader +See the property setting for "" property in pom.xml to find out the JDK version to compile with. +``` + git clone git@github.com:forcedotcom/dataloader.git + cd dataloader + git submodule init + git submodule update + mvn clean package -DskipTests + or + ./dlbuilder.sh +``` -The process-conf.xml file can be used to define properties for multiple processes. Look at src/main/nsis/samples/conf/process-conf.xml for examples on how to configure it. The way to run a process defined in process-conf.xml is to specify process name on command line like this: +`dataloader_v.zip` will be created in the root directory of the local git clone. - java -cp target/dataloader-xx.0-uber.jar -Dsalesforce.config.dir=CONFIG_DIR com.salesforce.dataloader.process.ProcessRunner process.name=opportunityUpsertProcess +# Debugging Data Loader +To run data loader for debugging with an IDE (remote debugging, port 5005), run the following command in the git clone root folder: + ./rundl.sh -d + + OR + + java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005 -cp target/dataloader-x.y.z.jar com.salesforce.dataloader.process.DataLoaderRunner salesforce.config.dir=./configs -# Test Data Loader +# Testing Data Loader See the [testing wiki](https://github.com/forcedotcom/dataloader/wiki/Testing-Dataloader) # Resources -For more information, see the [wiki](http://wiki.apexdevnet.com/index.php/Tools), or the [Data Loader Developer's Guide](https://na1.salesforce.com/help/doc/en/salesforce_data_loader.pdf). +For more information, see the [Salesforce Data Loader Guide](https://na1.salesforce.com/help/doc/en/salesforce_data_loader.pdf). Questions can be directed to the [open source forum](https://developer.salesforce.com/forums?feedtype=RECENT&dc=APIs_and_Integration&criteria=ALLQUESTIONS&#!/feedtype=RECENT&criteria=ALLQUESTIONS&). -# Dependencies +# Dependencies and plugins -Force Web Service Connector version is here. [Force Wsc and Patner API](https://mvnrepository.com/artifact/com.force.api) +Update SWT by running `python3 /updateSWT.py `. Requires python 3.9 or later. +All other dependencies and plugins are downloaded by maven from the central maven repo. Run `mvn versions:display-dependency-updates` to see which dependencies need an update. It will list all dependencies whose specified version in pom.xml needs an update. Run `mvn versions:use-latest-releases` to update these dependencies. Run `mvn versions:display-plugin-updates` again to check which plugins still need an update and update their versions manually. diff --git a/contrib/scripts/installjava.bat b/contrib/scripts/installjava.bat new file mode 100644 index 000000000..e37e5abc0 --- /dev/null +++ b/contrib/scripts/installjava.bat @@ -0,0 +1,70 @@ +@echo off +echo . +echo . +echo NOTICE: +echo Read this notice carefully before proceeding. +echo . +echo THIS SOFTWARE IS NOT SUPPORTED BY SALESFORCE INC. +echo SALESFORCE INC. DOES NOT ENDORSE OR SUPPORT ANY 3RD PARTY SOFTWARE DOWNLOADED OR INSTALLED BY THIS SOFTWARE. +echo THIS SOFTWARE DOWNLOADS AND INSTALLS 3RD PARTY SOFTWARE INCLUDING BUT NOT LIMITED TO SCOOP, 7ZIP, GIT, JAVA. +echo . +echo Copyright (c) 2023, Salesforce, inc. +echo . +echo THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED +echo PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +echo ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +echo TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +echo NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR THE USE OF 3RD PARTY SOFTWARE +echo DOWNLOADED OR INSTALLED BY THIS SOFTWARE, REGARDLESS OF BEING ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +echo . +echo . +CALL :promptForJavaInstallation +pause +EXIT /b %ERRORLEVEL% + +:promptForJavaInstallation + echo . + echo The script will download and install Temurin Java and associated dependencies + echo using scoop from from https://github.com/ScoopInstaller/Scoop . + echo It will also install the needed dependencies: 7zip, and git client. + set prompt="Do you want to install Java? [Yes/No] " + SET /p REPLY=%prompt% + if /I "%REPLY%"=="Y" goto :installScoopAndJava + if /I "%REPLY%"=="Yes" goto :installScoopAndJava + if /I "%REPLY%"=="N" EXIT 0 + if /I "%REPLY%"=="No" EXIT 0 + echo Type Yes or No. + goto :promptForJavaInstallation + EXIT /b 0 + +:installScoopAndJava + set JRE_NAME=temurin17-jre + set "SCOOP_CMD_FILE=%UserProfile%\scoop\shims\scoop.ps1" + if NOT EXIST %SCOOP_CMD_FILE% ( + Powershell.exe -executionpolicy remotesigned "irm get.scoop.sh | iex" + ) + if NOT "%ERRORLEVEL%" == "0" ( + goto :exitWithJavaDownloadMessage + ) + echo going to install 7zip + CALL :execScoop install 7zip + echo going to update 7zip + CALL :execScoop update 7zip + echo going to install git + CALL :execScoop install git + echo going to update git + CALL :execScoop update git + echo going to remove java bucket + CALL :execScoop bucket rm java + echo going to add java bucket + CALL :execScoop bucket add java + echo going to install java + CALL :execScoop install %JRE_NAME% + echo going to update java + CALL :execScoop update %JRE_NAME% + echo installed JRE + EXIT /b 0 + +:execScoop + powershell.exe -noprofile -ex unrestricted -file "%SCOOP_CMD_FILE%" %* + EXIT /b %ERRORLEVEL% diff --git a/contrib/scripts/installjava.command b/contrib/scripts/installjava.command new file mode 100755 index 000000000..34bad2ce0 --- /dev/null +++ b/contrib/scripts/installjava.command @@ -0,0 +1,69 @@ +#!/bin/bash + +promptForJavaInstallation() { + while true + do + prompt="Do you want to install Eclipse Temurin Java using sdkman from sdkman.io ? [Yes/No] " + read -r -p "$prompt" input + case $input in + [yY][eE][sS]|[yY]) + installSdkmanAndJava + break + ;; + [nN][oO]|[nN]) + exit 0 + ;; + *) + echo "Type Yes or No." + ;; + esac + done +} + +installSdkmanAndJava() { + source "$HOME/.sdkman/bin/sdkman-init.sh" >& /dev/null + sdk version >& /dev/null + if [ $? -eq 0 ] + then + sdk selfupdate force + else + curl -s "https://get.sdkman.io" | bash + fi + + if [ $? -eq 0 ] # succeeded in installing sdkman + then + source "$HOME/.sdkman/bin/sdkman-init.sh" + sdk install java + fi + + if [ $? -ne 0 ] # did not successfully install sdkman or java + then + echo "Unable to install Java. Try manual installation." + echo + fi +} + +echo +echo +echo NOTICE: +echo Read this notice carefully before proceeding. +echo . +echo THIS SOFTWARE IS NOT SUPPORTED BY SALESFORCE INC. +echo SALESFORCE INC. DOES NOT ENDORSE OR SUPPORT ANY 3RD PARTY SOFTWARE DOWNLOADED OR INSTALLED BY THIS SOFTWARE. +echo THIS SOFTWARE DOWNLOADS AND INSTALLS 3RD PARTY SOFTWARE INCLUDING BUT NOT LIMITED TO SDKMAN, JAVA. +echo +echo THIS SOFTWARE IS NOT SUPPORTED BY SALESFORCE INC. +echo +echo "Copyright (c) 2023, Salesforce, inc." +echo +echo "THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED \ +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A \ +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR \ +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED \ +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) \ +HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING \ +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE OR THE USE OF 3RD PARTY SOFTWARE \ +DOWNLOADED OR INSTALLED BY THIS SOFTWARE, REGARDLESS OF BEING ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +echo +echo +promptForJavaInstallation \ No newline at end of file diff --git a/contrib/xlstocsv/pom.xml b/contrib/xlstocsv/pom.xml new file mode 100644 index 000000000..da4915f59 --- /dev/null +++ b/contrib/xlstocsv/pom.xml @@ -0,0 +1,203 @@ + + 4.0.0 + xls + xlstocsv + jar + 0.1 + XLS To CSV converter + TBD + + + + + + + 17 + UTF-8 + + + + + + maven-eclipse-repo + https://maven-eclipse.github.io/maven + + + + + + + + org.apache.logging.log4j + log4j-core + 2.20.0 + + + + org.apache.poi + poi + 5.2.3 + + + + org.apache.commons + commons-csv + 1.10.0 + + + + org.apache.poi + poi-ooxml + 5.2.3 + + + + + install + + + + + + + org.apache.maven.plugins + maven-clean-plugin + 3.3.1 + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.1 + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.1 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + contrib/** + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.12.1 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.4.0 + + + enforce-maven + + enforce + + + + + 3.6 + + + + + + + + + + maven-compiler-plugin + 3.11.0 + + true + + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 3.5.0 + + false + + + local.swt:swt*: + + + + + xls.XLSToCSVConverter + + xls.XLSToCSVConverter + true + all-permissions + ${project.name} + + + + true + + + org.apache.logging.log4j:log4j-core + + ** + + + + org.apache.logging.log4j:log4j-api + + ** + + + + org.springframework:spring-beans + + ** + + + + *:* + + **/Log4j2Plugins.dat + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + + + + + + + make-shaded-jar + package + + shade + + + + + + + + + diff --git a/contrib/xlstocsv/src/main/java/xls/XLSToCSVConverter.java b/contrib/xlstocsv/src/main/java/xls/XLSToCSVConverter.java new file mode 100644 index 000000000..55b243cb7 --- /dev/null +++ b/contrib/xlstocsv/src/main/java/xls/XLSToCSVConverter.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package xls; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; + +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +public class XLSToCSVConverter { + public static void main(String[] args) { + System.out.println("XLSToCSVConverter called"); + String path = args[0]; + System.out.println("args[0] = " + args[0]); + + String[] fileComponents = path.split("\\.csv"); + if (path.endsWith("csv")) { + fileComponents = path.split("\\.csv"); + } + if (path.endsWith("xls")) { + fileComponents = path.split("\\.xls"); + } + if (path.endsWith("xlsx")) { + fileComponents = path.split("\\.xlsx"); + } + + String xlsName = fileComponents[0] + ".xls"; + if (!Files.exists(Path.of(xlsName))) { + xlsName = fileComponents[0] + ".xlsx"; + } + String csvName = fileComponents[0] + ".csv"; + if (!Files.exists(Path.of(xlsName))) { + System.out.println("File " + xlsName + " not found at " + Path.of(xlsName)); + System.exit(-1); // did not find a xls or xlsx file + } + + + // Creating an inputFile object with specific file path + File inputFile = new File(xlsName); + + // Creating an outputFile object to write excel data to csv + File outputFile = new File(csvName); + + // For storing data into CSV files + StringBuffer data = new StringBuffer(); + + try { + // Creating input stream + FileInputStream fis = new FileInputStream(inputFile); + Workbook workbook = null; + + // Get the workbook object for Excel file based on file format + if (inputFile.getName().endsWith(".xlsx")) { + workbook = new XSSFWorkbook(fis); + } else if (inputFile.getName().endsWith(".xls")) { + workbook = new HSSFWorkbook(fis); + } else { + fis.close(); + throw new Exception("File not supported!"); + } + + // Get first sheet from the workbook + Sheet sheet = workbook.getSheetAt(0); + + // Iterate through each rows from first sheet + Iterator rowIterator = sheet.iterator(); + + while (rowIterator.hasNext()) { + Row row = rowIterator.next(); + boolean firstCellInRow = true; + // For each row, iterate through each columns + Iterator cellIterator = row.cellIterator(); + while (cellIterator.hasNext()) { + if (firstCellInRow) { + firstCellInRow = false; + } else { + data.append(","); + } + + Cell cell = cellIterator.next(); + data.append("\""); + switch (cell.getCellType()) { + case BOOLEAN: + data.append(cell.getBooleanCellValue()); + break; + + case NUMERIC: + data.append(cell.getNumericCellValue()); + break; + + case STRING: + data.append(cell.getStringCellValue()); + break; + + case BLANK: + data.append(""); + break; + + default: + data.append(cell); + } + data.append("\""); + } + // appending new line after each row + data.append('\n'); + } + + FileOutputStream fos = new FileOutputStream(outputFile); + fos.write(data.toString().getBytes()); + fos.close(); + + } catch (Exception e) { + e.printStackTrace(); + } + + System.out.println("Conversion of an Excel file to CSV file is done!"); + } +} \ No newline at end of file diff --git a/dlbuilder.sh b/dlbuilder.sh new file mode 100755 index 000000000..4d324afdb --- /dev/null +++ b/dlbuilder.sh @@ -0,0 +1,23 @@ +#!/bin/sh +# script parameters +# + +run_mvn() { + # run a mvn build to download dependencies from the central maven repo + mvn clean compile + # remove vulnerable class from log4j jar file + log4j_version_num=$(awk '/log4j-core/{getline; print}' pom.xml | awk '{$1=$1};1' ) + log4j_version_num=${log4j_version_num#}; + log4j_version_num=${log4j_version_num%}; + + echo "removing JndiLookup.class from ${log4j_version_num}" + zip -q -d "${HOME}/.m2/repository/org/apache/logging/log4j/log4j-core/${log4j_version_num}/log4j-core-${log4j_version_num}.jar org/apache/logging/log4j/core/lookup/JndiLookup.class" + zip -q -d "${HOME}/.m2/repository/org/apache/logging/log4j/log4j-core/${log4j_version_num}/log4j-core-${log4j_version_num}.jar org/apache/logging/log4j/core/appender/mom/JmsAppender.class" + zip -q -d "${HOME}/.m2/repository/org/apache/logging/log4j/log4j-core/${log4j_version_num}/log4j-core-${log4j_version_num}.jar org/apache/logging/log4j/core/appender/db/jdbc/JdbcAppender.class" + + # build uber jar + mvn clean package +} + +################# +run_mvn \ No newline at end of file diff --git a/java_pid11058.hprof b/java_pid11058.hprof new file mode 100644 index 000000000..1ecdcbe94 Binary files /dev/null and b/java_pid11058.hprof differ diff --git a/local-proj-repo/autoitx4java/autoitx4java/1/autoitx4java-1.jar b/local-proj-repo/autoitx4java/autoitx4java/1/autoitx4java-1.jar deleted file mode 100644 index a8152b915..000000000 Binary files a/local-proj-repo/autoitx4java/autoitx4java/1/autoitx4java-1.jar and /dev/null differ diff --git a/local-proj-repo/autoitx4java/autoitx4java/1/autoitx4java-1.pom b/local-proj-repo/autoitx4java/autoitx4java/1/autoitx4java-1.pom deleted file mode 100644 index b5a8fc18c..000000000 --- a/local-proj-repo/autoitx4java/autoitx4java/1/autoitx4java-1.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - autoitx4java - autoitx4java - 1 - POM was created from install:install-file - diff --git a/local-proj-repo/autoitx4java/autoitx4java/maven-metadata-local.xml b/local-proj-repo/autoitx4java/autoitx4java/maven-metadata-local.xml deleted file mode 100644 index 63d3e8f2a..000000000 --- a/local-proj-repo/autoitx4java/autoitx4java/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - autoitx4java - autoitx4java - - 1 - - 1 - - 20120809051836 - - diff --git a/local-proj-repo/local/swt/swtlinux_aarch64/4.33/_remote.repositories b/local-proj-repo/local/swt/swtlinux_aarch64/4.33/_remote.repositories new file mode 100644 index 000000000..b909250aa --- /dev/null +++ b/local-proj-repo/local/swt/swtlinux_aarch64/4.33/_remote.repositories @@ -0,0 +1,4 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Dec 02 15:24:29 PST 2024 +swtlinux_aarch64-4.33.jar>= +swtlinux_aarch64-4.33.pom>= diff --git a/local-proj-repo/local/swt/swtlinux_aarch64/4.33/swtlinux_aarch64-4.33.jar b/local-proj-repo/local/swt/swtlinux_aarch64/4.33/swtlinux_aarch64-4.33.jar new file mode 100644 index 000000000..f103b4975 Binary files /dev/null and b/local-proj-repo/local/swt/swtlinux_aarch64/4.33/swtlinux_aarch64-4.33.jar differ diff --git a/local-proj-repo/local/swt/swtlinux_aarch64/4.33/swtlinux_aarch64-4.33.pom b/local-proj-repo/local/swt/swtlinux_aarch64/4.33/swtlinux_aarch64-4.33.pom new file mode 100644 index 000000000..21c743c43 --- /dev/null +++ b/local-proj-repo/local/swt/swtlinux_aarch64/4.33/swtlinux_aarch64-4.33.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + local.swt + swtlinux_aarch64 + 4.33 + POM was created from install:install-file + diff --git a/local-proj-repo/local/swt/swtlinux_aarch64/maven-metadata-local.xml b/local-proj-repo/local/swt/swtlinux_aarch64/maven-metadata-local.xml new file mode 100644 index 000000000..5dcdde621 --- /dev/null +++ b/local-proj-repo/local/swt/swtlinux_aarch64/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + local.swt + swtlinux_aarch64 + + 4.33 + + 4.33 + + 20241202232428 + + diff --git a/local-proj-repo/local/swt/swtlinux_x86_64/4.33/_remote.repositories b/local-proj-repo/local/swt/swtlinux_x86_64/4.33/_remote.repositories new file mode 100644 index 000000000..32922fc88 --- /dev/null +++ b/local-proj-repo/local/swt/swtlinux_x86_64/4.33/_remote.repositories @@ -0,0 +1,4 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Dec 02 15:24:24 PST 2024 +swtlinux_x86_64-4.33.jar>= +swtlinux_x86_64-4.33.pom>= diff --git a/local-proj-repo/local/swt/swtlinux_x86_64/4.33/swtlinux_x86_64-4.33.jar b/local-proj-repo/local/swt/swtlinux_x86_64/4.33/swtlinux_x86_64-4.33.jar new file mode 100644 index 000000000..41ef1d535 Binary files /dev/null and b/local-proj-repo/local/swt/swtlinux_x86_64/4.33/swtlinux_x86_64-4.33.jar differ diff --git a/local-proj-repo/local/swt/swtlinux_x86_64/4.33/swtlinux_x86_64-4.33.pom b/local-proj-repo/local/swt/swtlinux_x86_64/4.33/swtlinux_x86_64-4.33.pom new file mode 100644 index 000000000..2f05c61e3 --- /dev/null +++ b/local-proj-repo/local/swt/swtlinux_x86_64/4.33/swtlinux_x86_64-4.33.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + local.swt + swtlinux_x86_64 + 4.33 + POM was created from install:install-file + diff --git a/local-proj-repo/local/swt/swtlinux_x86_64/maven-metadata-local.xml b/local-proj-repo/local/swt/swtlinux_x86_64/maven-metadata-local.xml new file mode 100644 index 000000000..ecf326b55 --- /dev/null +++ b/local-proj-repo/local/swt/swtlinux_x86_64/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + local.swt + swtlinux_x86_64 + + 4.33 + + 4.33 + + 20241202232424 + + diff --git a/local-proj-repo/local/swt/swtmac_aarch64/4.33/_remote.repositories b/local-proj-repo/local/swt/swtmac_aarch64/4.33/_remote.repositories new file mode 100644 index 000000000..2a0c62f73 --- /dev/null +++ b/local-proj-repo/local/swt/swtmac_aarch64/4.33/_remote.repositories @@ -0,0 +1,4 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Dec 02 15:24:19 PST 2024 +swtmac_aarch64-4.33.jar>= +swtmac_aarch64-4.33.pom>= diff --git a/local-proj-repo/local/swt/swtmac_aarch64/4.33/swtmac_aarch64-4.33.jar b/local-proj-repo/local/swt/swtmac_aarch64/4.33/swtmac_aarch64-4.33.jar new file mode 100644 index 000000000..7cbaff79e Binary files /dev/null and b/local-proj-repo/local/swt/swtmac_aarch64/4.33/swtmac_aarch64-4.33.jar differ diff --git a/local-proj-repo/local/swt/swtmac_aarch64/4.33/swtmac_aarch64-4.33.pom b/local-proj-repo/local/swt/swtmac_aarch64/4.33/swtmac_aarch64-4.33.pom new file mode 100644 index 000000000..6af2bb4df --- /dev/null +++ b/local-proj-repo/local/swt/swtmac_aarch64/4.33/swtmac_aarch64-4.33.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + local.swt + swtmac_aarch64 + 4.33 + POM was created from install:install-file + diff --git a/local-proj-repo/local/swt/swtmac_aarch64/maven-metadata-local.xml b/local-proj-repo/local/swt/swtmac_aarch64/maven-metadata-local.xml new file mode 100644 index 000000000..2744d2c3b --- /dev/null +++ b/local-proj-repo/local/swt/swtmac_aarch64/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + local.swt + swtmac_aarch64 + + 4.33 + + 4.33 + + 20241202232419 + + diff --git a/local-proj-repo/local/swt/swtmac_x86_64/4.33/_remote.repositories b/local-proj-repo/local/swt/swtmac_x86_64/4.33/_remote.repositories new file mode 100644 index 000000000..485134802 --- /dev/null +++ b/local-proj-repo/local/swt/swtmac_x86_64/4.33/_remote.repositories @@ -0,0 +1,4 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Dec 02 15:24:14 PST 2024 +swtmac_x86_64-4.33.jar>= +swtmac_x86_64-4.33.pom>= diff --git a/local-proj-repo/local/swt/swtmac_x86_64/4.33/swtmac_x86_64-4.33.jar b/local-proj-repo/local/swt/swtmac_x86_64/4.33/swtmac_x86_64-4.33.jar new file mode 100644 index 000000000..f59885fc0 Binary files /dev/null and b/local-proj-repo/local/swt/swtmac_x86_64/4.33/swtmac_x86_64-4.33.jar differ diff --git a/local-proj-repo/local/swt/swtmac_x86_64/4.33/swtmac_x86_64-4.33.pom b/local-proj-repo/local/swt/swtmac_x86_64/4.33/swtmac_x86_64-4.33.pom new file mode 100644 index 000000000..7b70483f2 --- /dev/null +++ b/local-proj-repo/local/swt/swtmac_x86_64/4.33/swtmac_x86_64-4.33.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + local.swt + swtmac_x86_64 + 4.33 + POM was created from install:install-file + diff --git a/local-proj-repo/local/swt/swtmac_x86_64/maven-metadata-local.xml b/local-proj-repo/local/swt/swtmac_x86_64/maven-metadata-local.xml new file mode 100644 index 000000000..d6a7e75dd --- /dev/null +++ b/local-proj-repo/local/swt/swtmac_x86_64/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + local.swt + swtmac_x86_64 + + 4.33 + + 4.33 + + 20241202232413 + + diff --git a/local-proj-repo/local/swt/swtwin32_x86_64/4.33/_remote.repositories b/local-proj-repo/local/swt/swtwin32_x86_64/4.33/_remote.repositories new file mode 100644 index 000000000..ed8ad2e20 --- /dev/null +++ b/local-proj-repo/local/swt/swtwin32_x86_64/4.33/_remote.repositories @@ -0,0 +1,4 @@ +#NOTE: This is a Maven Resolver internal implementation file, its format can be changed without prior notice. +#Mon Dec 02 15:24:08 PST 2024 +swtwin32_x86_64-4.33.jar>= +swtwin32_x86_64-4.33.pom>= diff --git a/local-proj-repo/local/swt/swtwin32_x86_64/4.33/swtwin32_x86_64-4.33.jar b/local-proj-repo/local/swt/swtwin32_x86_64/4.33/swtwin32_x86_64-4.33.jar new file mode 100644 index 000000000..1a5ec80c7 Binary files /dev/null and b/local-proj-repo/local/swt/swtwin32_x86_64/4.33/swtwin32_x86_64-4.33.jar differ diff --git a/local-proj-repo/local/swt/swtwin32_x86_64/4.33/swtwin32_x86_64-4.33.pom b/local-proj-repo/local/swt/swtwin32_x86_64/4.33/swtwin32_x86_64-4.33.pom new file mode 100644 index 000000000..e71b63d25 --- /dev/null +++ b/local-proj-repo/local/swt/swtwin32_x86_64/4.33/swtwin32_x86_64-4.33.pom @@ -0,0 +1,9 @@ + + + 4.0.0 + local.swt + swtwin32_x86_64 + 4.33 + POM was created from install:install-file + diff --git a/local-proj-repo/local/swt/swtwin32_x86_64/maven-metadata-local.xml b/local-proj-repo/local/swt/swtwin32_x86_64/maven-metadata-local.xml new file mode 100644 index 000000000..773beab71 --- /dev/null +++ b/local-proj-repo/local/swt/swtwin32_x86_64/maven-metadata-local.xml @@ -0,0 +1,12 @@ + + + local.swt + swtwin32_x86_64 + + 4.33 + + 4.33 + + 20241202232406 + + diff --git a/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329-sources.jar b/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329-sources.jar deleted file mode 100644 index 3fcd5f62d..000000000 Binary files a/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329.jar b/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329.jar deleted file mode 100644 index 913c98f89..000000000 Binary files a/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329.pom b/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329.pom deleted file mode 100644 index 70c26cfb4..000000000 --- a/local-proj-repo/org/eclipse/core.commands/3.6.1.v20120521-2329/core.commands-3.6.1.v20120521-2329.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - core.commands - 3.6.1.v20120521-2329 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/core.commands/maven-metadata-local.xml b/local-proj-repo/org/eclipse/core.commands/maven-metadata-local.xml deleted file mode 100644 index 39bd9fea4..000000000 --- a/local-proj-repo/org/eclipse/core.commands/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - core.commands - - 3.6.1.v20120521-2329 - - 3.6.1.v20120521-2329 - - 20120809070735 - - diff --git a/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841-sources.jar b/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841-sources.jar deleted file mode 100644 index 31a364a35..000000000 Binary files a/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841.jar b/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841.jar deleted file mode 100644 index 34f86eea2..000000000 Binary files a/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841.pom b/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841.pom deleted file mode 100644 index f8dc676bf..000000000 --- a/local-proj-repo/org/eclipse/equinox.common/3.6.100.v20120522-1841/equinox.common-3.6.100.v20120522-1841.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - equinox.common - 3.6.100.v20120522-1841 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/equinox.common/maven-metadata-local.xml b/local-proj-repo/org/eclipse/equinox.common/maven-metadata-local.xml deleted file mode 100644 index 9d9885297..000000000 --- a/local-proj-repo/org/eclipse/equinox.common/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - equinox.common - - 3.6.100.v20120522-1841 - - 3.6.100.v20120522-1841 - - 20120809065635 - - diff --git a/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329-sources.jar b/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329-sources.jar deleted file mode 100644 index 16a859544..000000000 Binary files a/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329.jar b/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329.jar deleted file mode 100644 index aefe2d197..000000000 Binary files a/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329.pom b/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329.pom deleted file mode 100644 index d5e08c310..000000000 --- a/local-proj-repo/org/eclipse/jface/3.8.0.v20120521-2329/jface-3.8.0.v20120521-2329.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - jface - 3.8.0.v20120521-2329 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/jface/maven-metadata-local.xml b/local-proj-repo/org/eclipse/jface/maven-metadata-local.xml deleted file mode 100644 index 68f773646..000000000 --- a/local-proj-repo/org/eclipse/jface/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - jface - - 3.8.0.v20120521-2329 - - 3.8.0.v20120521-2329 - - 20120809065507 - - diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2-sources.jar b/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2-sources.jar deleted file mode 100644 index 4cb62f383..000000000 Binary files a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2.jar b/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2.jar deleted file mode 100644 index b2f19230f..000000000 Binary files a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2.pom b/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2.pom deleted file mode 100644 index b0d1ecbef..000000000 --- a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/4.2/swt-cocoa-macosx-x86_64-4.2.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - swt-cocoa-macosx-x86_64 - 4.2 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/maven-metadata-local.xml b/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/maven-metadata-local.xml deleted file mode 100644 index 70d9d27eb..000000000 --- a/local-proj-repo/org/eclipse/swt-cocoa-macosx-x86_64/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - swt-cocoa-macosx-x86_64 - - 4.2 - - 4.2 - - 20120809062434 - - diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2-sources.jar b/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2-sources.jar deleted file mode 100644 index 62b24d7dc..000000000 Binary files a/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2.jar b/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2.jar deleted file mode 100644 index a1d359d20..000000000 Binary files a/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2.pom b/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2.pom deleted file mode 100644 index 93d9e2252..000000000 --- a/local-proj-repo/org/eclipse/swt-cocoa-macosx/4.2/swt-cocoa-macosx-4.2.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - swt-cocoa-macosx - 4.2 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/swt-cocoa-macosx/maven-metadata-local.xml b/local-proj-repo/org/eclipse/swt-cocoa-macosx/maven-metadata-local.xml deleted file mode 100644 index 5590d434f..000000000 --- a/local-proj-repo/org/eclipse/swt-cocoa-macosx/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - swt-cocoa-macosx - - 4.2 - - 4.2 - - 20120809062317 - - diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2-sources.jar b/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2-sources.jar deleted file mode 100644 index df3a873ee..000000000 Binary files a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2.jar b/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2.jar deleted file mode 100644 index e60d68b6a..000000000 Binary files a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2.pom b/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2.pom deleted file mode 100644 index b22f07068..000000000 --- a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/4.2/swt-gtk-linux-x86-4.2.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - swt-gtk-linux-x86 - 4.2 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/maven-metadata-local.xml b/local-proj-repo/org/eclipse/swt-gtk-linux-x86/maven-metadata-local.xml deleted file mode 100644 index c446c511b..000000000 --- a/local-proj-repo/org/eclipse/swt-gtk-linux-x86/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - swt-gtk-linux-x86 - - 4.2 - - 4.2 - - 20120809062531 - - diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2-sources.jar b/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2-sources.jar deleted file mode 100644 index b07aa90da..000000000 Binary files a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2.jar b/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2.jar deleted file mode 100644 index ce34a176a..000000000 Binary files a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2.pom b/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2.pom deleted file mode 100644 index 552386254..000000000 --- a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/4.2/swt-gtk-linux-x86_64-4.2.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - swt-gtk-linux-x86_64 - 4.2 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/maven-metadata-local.xml b/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/maven-metadata-local.xml deleted file mode 100644 index 9674a805f..000000000 --- a/local-proj-repo/org/eclipse/swt-gtk-linux-x86_64/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - swt-gtk-linux-x86_64 - - 4.2 - - 4.2 - - 20120809062617 - - diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2-sources.jar b/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2-sources.jar deleted file mode 100644 index e13073e2a..000000000 Binary files a/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2.jar b/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2.jar deleted file mode 100644 index ecd06d116..000000000 Binary files a/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2.pom b/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2.pom deleted file mode 100644 index 2f2a357f9..000000000 --- a/local-proj-repo/org/eclipse/swt-win32-win32-x86/4.2/swt-win32-win32-x86-4.2.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - swt-win32-win32-x86 - 4.2 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86/maven-metadata-local.xml b/local-proj-repo/org/eclipse/swt-win32-win32-x86/maven-metadata-local.xml deleted file mode 100644 index 5b188acf1..000000000 --- a/local-proj-repo/org/eclipse/swt-win32-win32-x86/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - swt-win32-win32-x86 - - 4.2 - - 4.2 - - 20120809062709 - - diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2-sources.jar b/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2-sources.jar deleted file mode 100644 index 3236bd902..000000000 Binary files a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2-sources.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2.jar b/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2.jar deleted file mode 100644 index 7aa3695be..000000000 Binary files a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2.jar and /dev/null differ diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2.pom b/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2.pom deleted file mode 100644 index 30e957485..000000000 --- a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/4.2/swt-win32-win32-x86_64-4.2.pom +++ /dev/null @@ -1,9 +0,0 @@ - - - 4.0.0 - org.eclipse - swt-win32-win32-x86_64 - 4.2 - POM was created from install:install-file - diff --git a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/maven-metadata-local.xml b/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/maven-metadata-local.xml deleted file mode 100644 index 0397a3a7c..000000000 --- a/local-proj-repo/org/eclipse/swt-win32-win32-x86_64/maven-metadata-local.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - org.eclipse - swt-win32-win32-x86_64 - - 4.2 - - 4.2 - - 20120809062748 - - diff --git a/make-pretty-dmg.sh b/make-pretty-dmg.sh deleted file mode 100755 index 2227fde08..000000000 --- a/make-pretty-dmg.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -BASEDIR="$1" -DMG_SRC_DIR="$2" -TIME=$(date +%s) -DMG_OUT="${BASEDIR}/target/ApexDataLoader.${TIME}.dmg" -CODE_SIGN="$3" - -chflags nouchg $DMG_SRC_DIR/* -chmod -R +w $DMG_SRC_DIR - -if [ "$CODE_SIGN" != "DONTSIGN" ] -then - codesign --force --verify --sign "$CODE_SIGN" $DMG_SRC_DIR/Data\ Loader.app -fi - -bash $BASEDIR/yoursway-create-dmg/create-dmg --volname "DataLoader" --volicon "$BASEDIR/src/main/resources/img/icons/icon_loader.icns" --background "$BASEDIR/src/main/nsis/installer-salesforce-dataLoader.png" --hide-extension Data\ Loader.app --icon-size 60 --window-size 400 300 --app-drop-link 300 210 --icon "Data Loader" 100 210 "$DMG_OUT" $DMG_SRC_DIR diff --git a/pom.xml b/pom.xml index 68701d0e7..ad123f82c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,834 +1,613 @@ - + + 4.0.0 com.force dataloader + 63.0.0 jar - 41.0.0 - Data Loader + Salesforce Data Loader https://github.com/forcedotcom/dataloader - salesforce.com + Salesforce, Inc. http://salesforce.com - 41.0.0 - 41.0.0 - 2017 - 1.8 - 1.8.0 - DONTSIGN - 1.14.3 - autoitplaceholder - 2.46 - 3.1.1.RELEASE + 17 UTF-8 - - http://testendpoint + https://testendpoint admin@org.com standard@org.com - + + + false - + ${project.build.testOutputDirectory}/testfiles + false - - - local-proj-repo - Project Local Repository - default - file://${basedir}/local-proj-repo/ - - - maven-eclipse-repo - http://maven-eclipse.github.io/maven - - - - - - codehaus-mojo - Codehaus Mojo - https://oss.sonatype.org/content/repositories/snapshots - - false - - - true - - - - + + - com.google.code.gson - gson - 2.3.1 - - - - autoitx4java - autoitx4java - 1 - test + local.swt + swtmac_aarch64 + 4.33 - + - com.force.api - force-wsc - ${force.wsc.version} + com.google.code.gson + gson + 2.11.0 + com.force.api force-partner-api - ${force.partner.api.version} + 63.0.0 + + + commons-logging + commons-logging + + + - com.h2database - h2 - 1.3.163 - test - - - - commons-beanutils - commons-beanutils - 1.8.3 - - - commons-dbcp - commons-dbcp - 1.4 + org.apache.commons + commons-dbcp2 + 2.13.0 + + + commons-logging + commons-logging + + + commons-io commons-io - 2.4 + 2.18.0 - + - junit - junit - 4.11 - test - - - - log4j - log4j - 1.2.16 + org.apache.logging.log4j + log4j-core + 2.24.3 + org.apache.httpcomponents httpclient - 4.5.2 - - - - org.eclipse - core.commands - 3.6.1.v20120521-2329 + 4.5.14 + + + commons-logging + commons-logging + + - - org.eclipse - equinox.common - 3.6.100.v20120522-1841 - - - org.eclipse - jface - 3.8.0.v20120521-2329 - - + org.springframework spring-context - ${spring.version} + 6.2.1 + - org.springframework - spring-expression - ${spring.version} - - - org.springframework - spring-jdbc - ${spring.version} - test + org.eclipse.platform + org.eclipse.jface + 3.35.100 + + + org.eclipse.platform + org.eclipse.swt + + + - org.testng - testng - 6.3.1 - test + org.apache.commons + commons-text + 1.13.0 + + + - org.mockito - mockito-all - 1.9.5 + com.h2database + h2 + 2.3.232 test + - net.sf.jacob-project - jacob - ${jacob.version} + junit + junit + 4.13.2 test + - net.sf.jacob-project - jacob - ${jacob.version} - x86 - dll + org.springframework + spring-jdbc + 6.2.1 test + - net.sf.jacob-project - jacob - ${jacob.version} - x64 - dll + org.mockito + mockito-core + 5.14.2 test - - - quartz - quartz - 1.5.2 - - + + + local-proj-repo + Project Local Repository + file://${basedir}/local-proj-repo/ + default + + + maven-eclipse-repo + https://maven-eclipse.github.io/maven + + + install - src/main/resources true + src/main/resources **/*.properties + + **/*.swp + **/*.*~* + - src/main/resources false + src/main/resources **/*.properties + **/*.swp + **/*.*~* - - src/main/nsis/bin - true - - - src/test/resources/testfilter.properties - - src/test/resources true + src/test/resources - + + - org.codehaus.mojo - osxappbundle-maven-plugin - 1.0-alpha-2 - - - org.apache.maven.plugins - maven-shade-plugin - 2.4.1 + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + org.apache.maven.plugins + maven-antrun-plugin + [3.1.0,) + + run + + + + + + + + + - - org.apache.maven.plugins - maven-resources-plugin - 2.4.3 - - false - - ${*} - - - pdf - swf - jpeg - jpg - png - - - - - + + + org.codehaus.mojo + versions-maven-plugin + 2.18.0 + + + - maven-assembly-plugin + org.apache.maven.plugins + maven-clean-plugin + 3.4.0 + + + + org.apache.maven.plugins + maven-deploy-plugin + 3.1.3 + + + + org.apache.maven.plugins + maven-install-plugin + 3.1.3 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 - - - com.salesforce.dataloader.process.DataLoaderRunner - - - - src/main/assembly/uber.xml - + + contrib/** + + + + + org.apache.maven.plugins + maven-site-plugin + 3.21.0 + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + 3.5.0 - make-uber-jar - package + enforce-maven - single + enforce + + + + 3.6 + + + + - maven-shade-plugin + org.apache.maven.plugins + maven-resources-plugin + 3.3.1 + + false + + ${*} + + + pdf + swf + jpeg + jpg + png + csv + + + + + + + maven-compiler-plugin + 3.13.0 + + true + + + + + + + + + com.mycila + license-maven-plugin + 4.6 + + + +
license.txt
+ + **/*.java + + + true + +
+
+
+ + check + package + + +
+ + + org.apache.maven.plugins + maven-shade-plugin + 3.6.0 + + false + + + local.swt:swt*: + + + + + com.salesforce.dataloader.process.DataLoaderRunner + + com.salesforce.dataloader.process.DataLoaderRunner + true + all-permissions + ${project.name} + + + + true + + + com.force.api:force-partner-api + + ** + + + + org.apache.logging.log4j:log4j-core + + ** + + + + org.apache.logging.log4j:log4j-api + + ** + + + + org.springframework:spring-beans + + ** + + + + *:* + + **/Log4j2Plugins.dat + META-INF/*.SF + META-INF/*.DSA + META-INF/*.RSA + META-INF/license.txt + META-INF/LICENSE.txt + META-INF/notice.txt + META-INF/NOTICE.txt + + + + + + + make-shaded-jar shade - - true - shade - - - *:* - - META-INF/*.SF - META-INF/*.DSA - META-INF/*.RSA - - - - - - com.salesforce.dataloader.process.DataLoaderRunner - - - META-INF/spring.handlers - - - META-INF/spring.schemas - - - META-INF/spring.tooling - - - + package + + - maven-compiler-plugin - 2.3.2 - - ${java.compile.version} - ${java.compile.version} - + org.apache.maven.plugins + maven-antrun-plugin + 3.1.0 + + + + run + + compile + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + zip + + run + + package + + + + + + + + + + + + test-cleanup + + run + + package + + + + + + + + org.apache.maven.plugins maven-surefire-plugin - 2.13 - - - org.apache.maven.surefire - surefire-junit4 - 2.13 - - + 3.5.2 - - - **/client/** - **/dyna/SObjectReferenceConverterTest.* - **/integration/** - **/mapping/LoadMapperTest.* - **/mapping/SOQLMapperTest.* - **/process/** - + + true + + ${basedir}/target/dataloader-${project.version}.jar + - - - - com.mycila.maven-license-plugin - maven-license-plugin - 1.8.0 + + + + + unit-tests + + test + + package -
license.txt
- - **/*.bat - **/*.html - **/*.txt - **/*.xml - **/*.md - **/*.nsi - **/*.properties - **/*.sh - windows-dependencies/** - local-proj-repo/** - src/main/resources/** - src/main/nsis/** - src/test/resources/** - yoursway-create-dmg/** - .gitignore - .gitmodules - + ${skip-unit-tests} + + + **/client/**, + **/dyna/SObjectReferenceConverterTest.*, + **/integration/**, + **/mapping/LoadMapperTest.*, + **/mapping/SOQLMapperTest.*, + **/process/** +
- - - package - - check - - - -
-
-
- - - - linux32 - - - Unix - x86 - - - - - org.eclipse.swt - org.eclipse.swt.gtk.linux.x86 - 4.5.1 - - - - - linux64 - - - Unix - amd64 - - - - - org.eclipse.swt - org.eclipse.swt.gtk.linux.x86_64 - 4.5.1 - - - - - mac32 - - - mac - x86 - - - - - org.eclipse.swt - org.eclipse.swt.cocoa.macosx.x86_64 - 4.5.1 - - - - - mac64 - - - mac - x86_64 - - - - - org.eclipse.swt - org.eclipse.swt.cocoa.macosx.x86_64 - 4.5.1 - - - - - win32 - - - Windows - x86 - - - - - org.eclipse.swt - org.eclipse.swt.win32.win32.x86 - 4.5.1 - - - - - win64 - - - Windows - amd64 - - - - - org.eclipse.swt - - org.eclipse.swt.win32.win32.x86 - 4.5.1 - - - - - - NSIS - - - Windows - - - - - - - - org.codehaus.mojo - nsis-maven-plugin - 1.0-SNAPSHOT - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - - com.akathist.maven.plugins.launch4j - launch4j-maven-plugin - 1.5.1 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 2.5.1 - - 1.8 - 1.8 - - - - com.akathist.maven.plugins.launch4j - launch4j-maven-plugin - 1.7.8 - - - l4j-clui - package - - launch4j - - - gui - true - ${project.artifactId}-${project.version}-uber.jar - ${project.build.directory}/${project.artifactId}-${project.version}.exe - http://java.com/download - - com.salesforce.dataloader.process.DataLoaderRunner - anything - - ${basedir}/src/main/resources/img/icons/dataloader.ico - - ${minJvmVersion} - preferJre - 32 - - - ${project.version}.0 - ${project.version} - ${project.name} - ${build.year} ${project.organization.name} - ${project.version}.0 - ${project.version}.0 - ${project.name} - ${project.organization.name} - /${project.artifactId}-${project.version} - ${project.artifactId}-${project.version}.exe - - - - - l4j-java-home - package - - launch4j - - - console - true - ../${project.artifactId}-${project.version}-uber.jar - ${project.build.directory}/${project.artifactId}-${project.version}-java-home.exe - http://java.com/download - - com.salesforce.dataloader.process.FindJavaRunner - anything - - ${basedir}/src/main/resources/img/icons/dataloader.ico - - ${minJvmVersion} - preferJre - 32 - - - ${project.version}.0 - ${project.version} - ${project.name} - ${build.year} ${project.organization.name} - ${project.version}.0 - ${project.version}.0 - ${project.name} - ${project.organization.name} - /${project.artifactId}-${project.version} - ${project.artifactId}-${project.version}-java-home.exe - - - - - - - - maven-antrun-plugin - 1.8 - - - copy-misc-files - prepare-package - - - - - Copying additional assets for MSI - - - - -!define CODESIGN "${codesign}" -!define PROJECT_BASEDIR "${basedir}" -!define PROJECT_BUILD_DIR "${basedir}\target" -!define PROJECT_FINAL_NAME "${project.artifactId}-${project.version}" -!define PROJECT_GROUP_ID "${project.groupId}" -!define PROJECT_ARTIFACT_ID "${project.artifactId}" -!define PROJECT_NAME "${project.name}" -!define PROJECT_VERSION "${project.version}" -!define PROJECT_URL "${project.url}" -!define PROJECT_ORGANIZATION_NAME "${project.organization.name}" -!define PROJECT_ORGANIZATION_URL "${project.organization.url}" -!define PROJECT_REG_KEY "SOFTWARE\${project.organization.name}\${project.name}\${project.version}" -!define PROJECT_REG_UNINSTALL_KEY "Software\Microsoft\Windows\CurrentVersion\Uninstall\${project.name} ${project.version}" -!define PROJECT_STARTMENU_FOLDER "$$SMPROGRAMS\${project.organization.name}\${project.name} ${project.version}" - - - - - - run - - - - sign-windows-exes - package - - - - - - - - - - - - - - - - - - - - - - - run - - - - - - - - - - installer-test-x86 - - - Windows - x86 - - - - ${basedir}\windows-dependencies\autoit\AutoItX\AutoItX3.dll - - - - - installer-test-amd64 - - - Windows - amd64 - - - - ${basedir}\windows-dependencies\autoit\AutoItX\AutoItX3_x64.dll - - - - - - mac-dmg - - - mac - - - - - - sh.tak.appbundler - appbundle-maven-plugin - 1.0.4 - - com.salesforce.dataloader.process.DataLoaderRunner - 1.8 - - -XstartOnFirstThread - -Dsalesforce.config.dir=Contents/Resources/conf - - ${basedir}/src/main/resources/img/icons/icon_loader.icns - - - - package - - bundle - - - - - - org.apache.maven.plugins - maven-antrun-plugin - 1.1 - - - package - - run - - - - - - Copying additional assets for DMG - - - - - - - - - org.codehaus.mojo - exec-maven-plugin - 1.1 - - - make-pretty-dmg - package - - bash - - make-pretty-dmg.sh - ${basedir} - ${project.build.directory}/${project.build.finalName} - ${codesign} - - - - exec - - - - - - - - - - - integration-test - - - - org.apache.maven.plugins - maven-surefire-plugin - 2.13 - - - org.apache.maven.surefire - surefire-junit4 - 2.13 - - + + + integration-tests + + test + + verify + !${skip-unit-tests} - - **/integration/** + **/action/**, + **/dao/**, + **/dyna/BooleanConverterTest.*, + **/dyna/DateConverterTest.*, + **/EncryptionUtil/**, + **/mapping/SOQLMapperTest.*, + **/util/** - - - - - - - + + + + + + com.github.ekryd.sortpom + sortpom-maven-plugin + 4.0.0 + + + + sort + + compile + + + + +
diff --git a/release/install/install.command b/release/install/install.command new file mode 100755 index 000000000..fb5266aee --- /dev/null +++ b/release/install/install.command @@ -0,0 +1,9 @@ +:; #!/bin/bash # +:; # +:; DL_INSTALL_ROOT="$(dirname "$(readlink -f "$0")")" # +:; source "${DL_INSTALL_ROOT}/util/util.sh" # +:; runDataLoader $@ run.mode=install # +:; exit $? # + +@echo off +CALL "%~dp0util\util.bat" :runDataLoader %* run.mode=install \ No newline at end of file diff --git a/rundl.sh b/rundl.sh new file mode 100755 index 000000000..b9757ab99 --- /dev/null +++ b/rundl.sh @@ -0,0 +1,30 @@ +#!/bin/bash -f + +debug="" +batchmodeargs="" +encryptionargs="" +configdir="salesforce.config.dir=./configs" +while getopts ":dbe:v:" flag +do + case "${flag}" in + d) + debug="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=0.0.0.0:5005,suspend=y" + ;; + b) + batchmodeargs="./.. upsertAccounts run.mode=batch" + ;; + e) + encryptionargs="run.mode=encrypt" + configdir="" + ;; + v) + version="${OPTARG}" + ;; + esac +done + +jarname="$(find ./target -name 'dataloader-[0-9][0-9].[0-9].[0-9].jar' | tail -1)" + + +java ${debug} -cp ${jarname} com.salesforce.dataloader.process.DataLoaderRunner ${batchmodeargs} ${configdir} ${encryptionargs} $@ +#java ${debug} -cp ${jarname} com.salesforce.dataloader.action.visitor.RESTTest diff --git a/runtests.sh b/runtests.sh new file mode 100755 index 000000000..031833d59 --- /dev/null +++ b/runtests.sh @@ -0,0 +1,91 @@ +#!/bin/bash -f + +usage() { + echo "Usage: " + echo "$0 [-d] [-D] [-c] [-i][-t ] " + echo "Listening on port 5005 for IDE to start the debugging session if -d is specified." + echo "Run 'mvn clean package' before encrypting password if -c is specified." + echo "Ignore test failures and continue test run if -i is specified." + exit 1 +} + +# To generate encrypted password +# build jar with the following command: +# mvn clean package -DskipTests +# run the following command to get encrypted password for the test admin account: +#java -cp target/dataloader-*.jar com.salesforce.dataloader.security.EncryptionUtil -e + +test="" +debug="" +debugEncryption="" +doClean="No" +#encryptionFile=${HOME}/.dataloader/dataLoader.key +encryptionFile= + +failfast="-Dsurefire.skipAfterFailureCount=5" + +while getopts ":dDicv:t:f:" flag +do + case "${flag}" in + c) + doClean="Yes" + ;; + d) + debug="-Dmaven.surefire.debug=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=0.0.0.0:5005" + ;; + D) + debugEncryption="-Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=0.0.0.0:5005,suspend=y" + ;; + t) + test="-Dskip-unit-tests=true -Dtest=com.salesforce.dataloader.${OPTARG}" + ;; + f) + encryptionFile="${OPTARG}" + ;; + i) + failfast="" + ;; + *) + usage + ;; + esac +done +shift $((OPTIND -1)) + +# $1 contains the test org URL +# $2 test admin user username +# $3 test regular user username +# $4 test admin and regular user encoded password + +if [ "$#" -lt 4 ]; then + usage +fi + +#echo $@ +if [ ${doClean} == "Yes" ]; then + mvn clean package -Dmaven.test.skip=true +fi + +if [ ! -d ./target ]; then + mvn package -Dmaven.test.skip=true +fi + +jarname="$(find ./target -name 'dataloader-[0-9][0-9].[0-9].[0-9].jar' | tail -1)" +#echo "password = ${4}" +encryptedPassword="$(java ${debugEncryption} -cp ${jarname} com.salesforce.dataloader.process.DataLoaderRunner run.mode=encrypt -e ${4} ${encryptionFile} | tail -1)" + +additionalOptions="" +for option in $@ +do + if [[ ${option} == -D* ]]; then + additionalOptions+=" " + additionalOptions+=${option} + fi +done + +# uncomment the following lines to debug issues with password encryption +#echo "encryptedPassword = ${encryptedPassword}" +#decryptedPassword="$(java ${debugEncryption} -cp ${jarname} com.salesforce.dataloader.process.DataLoaderRunner run.mode=encrypt -d ${encryptedPassword} ${encryptionFile} | tail -1)" +#echo "decryptedPassword = ${decryptedPassword}" + +mvn ${failfast} -Dtest.endpoint=${1} -Dtest.user.default=${2} -Dtest.user.restricted=${3} -Dtest.password=${encryptedPassword} -Dtest.encryptionFile=${encryptionFile} verify ${debug} ${test} ${additionalOptions} \ No newline at end of file diff --git a/setup.nsi b/setup.nsi deleted file mode 100644 index 963b14e25..000000000 --- a/setup.nsi +++ /dev/null @@ -1,329 +0,0 @@ -!addplugindir "windows-dependencies\UAC\plugins\x86-ansi" -!addplugindir "windows-dependencies\AccessControl\Plugins" -!include target\additional.nsh - -!define S_NAME "${PROJECT_FINAL_NAME}" -!define APPNAME "${PROJECT_NAME}" -!define S_DEFAULT_CONFIGFOLDER "$APPDATA\${PROJECT_ORGANIZATION_NAME}\${PROJECT_NAME} ${PROJECT_VERSION}" -!define S_DEFINSTDIR_USER "$LOCALAPPDATA\${PROJECT_ORGANIZATION_NAME}\${PROJECT_NAME}" -!define S_DEFINSTDIR_ADMIN "$PROGRAMFILES\${PROJECT_ORGANIZATION_NAME}\${PROJECT_NAME}" -!define UNINSTALLER_FULLPATH "$InstDir\Uninstaller.exe" - -!define MUI_ICON "src\main\resources\img\icons\dataloader.ico" -!define MUI_UNICON "src\main\resources\img\icons\dataloader.ico" - -Name "${APPNAME}" -OutFile "target\${S_NAME}.installer.exe" - -!include MUI2.nsh -!include windows-dependencies\UAC\UAC.nsh -!include nsDialogs.nsh -!include Sections.nsh - -!ifndef BCM_SETSHIELD -!define BCM_SETSHIELD 0x0000160C -!endif - -!define MUI_FINISHPAGE_NOAUTOCLOSE -!define MUI_UNFINISHPAGE_NOAUTOCLOSE - - -!macro BUILD_LANGUAGES -!insertmacro MUI_LANGUAGE "English" -!macroend - - -/*************************************************** -** Installer -***************************************************/ -!macro SetInstMode m -StrCpy $InstMode ${m} -call InstModeChanged -!macroend - -!macro BUILD_INSTALLER -!define MUI_COMPONENTSPAGE_NODESC -!define MUI_CUSTOMFUNCTION_GUIINIT GuiInit - -!insertmacro MUI_PAGE_LICENSE "src\main\nsis\license.rtf" -page custom InstModeSelectionPage_Create InstModeSelectionPage_Leave -!define MUI_PAGE_CUSTOMFUNCTION_PRE disableBack -!insertmacro MUI_PAGE_COMPONENTS -!insertmacro MUI_PAGE_DIRECTORY -!insertmacro MUI_PAGE_INSTFILES -!define MUI_FINISHPAGE_RUN -!define MUI_FINISHPAGE_RUN_FUNCTION FinishRun -!insertmacro MUI_PAGE_FINISH - -var InstMode ;0=current,1=all users - -Function InstModeChanged -SetShellVarContext CURRENT -${IfNotThen} ${Silent} ${|} StrCpy $InstDir "${S_DEFINSTDIR_USER}" ${|} -${If} $InstMode > 0 - SetShellVarContext ALL - ${IfNotThen} ${Silent} ${|} StrCpy $InstDir "${S_DEFINSTDIR_ADMIN}" ${|} -${EndIf} -FunctionEnd - -Function .onInit -!insertmacro UAC_PageElevation_OnInit -${If} ${UAC_IsInnerInstance} -${AndIfNot} ${UAC_IsAdmin} - SetErrorLevel 0x666666 ;special return value for outer instance so it knows we did not have admin rights - Quit -${EndIf} - -StrCpy $InstMode 0 -${IfThen} ${UAC_IsAdmin} ${|} StrCpy $InstMode 1 ${|} -call InstModeChanged - -${If} ${Silent} -${AndIf} $InstDir == "" ;defaults (for silent installs) - SetSilent normal - call InstModeChanged - SetSilent silent -${EndIf} -FunctionEnd - -Function GuiInit -!insertmacro UAC_PageElevation_OnGuiInit -FunctionEnd - -Function disableBack -${If} ${UAC_IsInnerInstance} - GetDlgItem $0 $HWNDParent 3 - EnableWindow $0 0 -${EndIf} -FunctionEnd - - - -Function RemoveNextBtnShield -GetDlgItem $0 $hwndParent 1 -SendMessage $0 ${BCM_SETSHIELD} 0 0 -FunctionEnd - -Function InstModeSelectionPage_Create -!insertmacro MUI_HEADER_TEXT_PAGE "Install for admins only?" "This choice determines who can use Data Loader on this machine, and whether Data Loader can run automated processes." -GetFunctionAddress $8 InstModeSelectionPage_OnClick -nsDialogs::Create /NOUNLOAD 1018 -Pop $9 -${NSD_OnBack} RemoveNextBtnShield -${NSD_CreateLabel} 0 20u 75% 20u "Do you have administrator rights on this machine?" -Pop $0 -System::Call "advapi32::GetUserName(t.r0,*i${NSIS_MAX_STRLEN})i" -${NSD_CreateRadioButton} 0 40u 75% 15u "No, or I'm not sure" -Pop $0 -nsDialogs::OnClick $0 $8 -nsDialogs::SetUserData $0 0 -SendMessage $0 ${BM_CLICK} 0 0 -${NSD_CreateRadioButton} 0 60u 75% 15u "Yes" -Pop $2 -${NSD_CreateLabel} 0 80u 95% 40u "If you select Yes, Data Loader will work only for users with administrator rights on this machine. If you're an advanced user with administrator rights on this machine, select Yes to enable Data Loader to run automated batch operations and scheduled processes." -Pop $0 -nsDialogs::OnClick $2 $8 -nsDialogs::SetUserData $2 1 -${IfThen} $InstMode <> 0 ${|} SendMessage $2 ${BM_CLICK} 0 0 ${|} -push $2 ;store allusers radio hwnd on stack -nsDialogs::Show -pop $2 -FunctionEnd - -Function InstModeSelectionPage_OnClick -pop $1 -nsDialogs::GetUserData $1 -pop $1 -GetDlgItem $0 $hwndParent 1 -SendMessage $0 ${BCM_SETSHIELD} 0 $1 -FunctionEnd - -Function InstModeSelectionPage_Leave -pop $0 ;get hwnd -push $0 ;and put it back -${NSD_GetState} $0 $9 -${If} $9 = 0 - !insertmacro SetInstMode 0 -${Else} - !insertmacro SetInstMode 1 - ${IfNot} ${UAC_IsAdmin} - GetDlgItem $9 $HWNDParent 1 - System::Call user32::GetFocus()i.s - EnableWindow $9 0 ;disable next button - !insertmacro UAC_PageElevation_RunElevated - EnableWindow $9 1 - System::Call user32::SetFocus(is) ;Do we need WM_NEXTDLGCTL or can we get away with this hack? - ${If} $2 = 0x666666 ;our special return, the new process was not admin after all - MessageBox mb_iconExclamation "You need to login with an account that is a member of the admin group to continue..." - Abort - ${ElseIf} $0 = 1223 ;cancel - Abort - ${Else} - ${If} $0 <> 0 - ${If} $0 = 1062 - MessageBox mb_iconstop "Unable to elevate, Secondary Logon service not running!" - ${Else} - MessageBox mb_iconstop "Unable to elevate, error $0" - ${EndIf} - Abort - ${EndIf} - ${EndIf} - Quit ;We now have a new process, the install will continue there, we have nothing left to do here - ${EndIf} -${EndIf} -FunctionEnd - -Function FinishRun -!insertmacro UAC_AsUser_ExecShell "" "$INSTDIR\${PROJECT_FINAL_NAME}.exe" "" "" "" -FunctionEnd - -Section "Required Files" - SectionIn RO - SetOutPath "$INSTDIR" - File "target\${PROJECT_FINAL_NAME}.exe" - File "target\${PROJECT_FINAL_NAME}-uber.jar" - File "src\main\resources\img\icons\dataloader.ico" - FileOpen $9 "${PROJECT_FINAL_NAME}.l4j.ini" w - ;Java Args here - FileWrite $9 "-Dappdata.dir=$\"$APPDATA$\"$\r$\n" - FileWrite $9 "-jar $\"$INSTDIR\${PROJECT_FINAL_NAME}-uber.jar$\"" - FileClose $9 - - SetOutPath "$INSTDIR\licenses" - File /r "src\main\nsis\licenses\" - - ; copy config files to appdata dir - CreateDirectory "${S_DEFAULT_CONFIGFOLDER}" - AccessControl::GrantOnFile "${S_DEFAULT_CONFIGFOLDER}" "(S-1-5-32-545)" "FullAccess" - SetOutPath "${S_DEFAULT_CONFIGFOLDER}" - File "target\config.properties" - -SectionEnd - -Section "Start menu shortcuts" -CreateShortcut "$smprograms\${APPNAME}.lnk" '"$INSTDIR\${PROJECT_FINAL_NAME}.exe"' '"${S_DEFAULT_CONFIGFOLDER}"' -SectionEnd - -Section Uninstaller - SetOutPath - - ${If} $InstMode = 0 - !insertmacro CreateUninstaller "${UNINSTALLER_FULLPATH}" 0 - ${Else} - !insertmacro CreateUninstaller "${UNINSTALLER_FULLPATH}" 1 - ${EndIf} - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" DisplayName "${APPNAME}" - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" UninstallString "${UNINSTALLER_FULLPATH}" - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" InstallLocation $InstDir - WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" NoModify 1 - WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" NoRepair 1 - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" DisplayIcon "$INSTDIR\dataloader.ico" - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" Publisher "${PROJECT_ORGANIZATION_NAME}" - WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" EstimatedSize 12178 - WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" DisplayVersion "${PROJECT_VERSION}" -SectionEnd - -Section "Command line tools" CommandLineTools - SetOutPath "$INSTDIR\bin" - File "target\classes\encrypt.bat" - File "target\classes\process.bat" - File "target\${PROJECT_FINAL_NAME}-java-home.exe" -SectionEnd - -Section "Samples" Samples - SetOutPath "$INSTDIR\samples" - File /r "src\main\nsis\samples\" -SectionEnd - -!macroend - -/*************************************************** -** Uninstaller -***************************************************/ -!macro BUILD_UNINSTALLER -!insertmacro MUI_UNPAGE_CONFIRM -!insertmacro MUI_UNPAGE_INSTFILES -;!insertmacro MUI_UNPAGE_FINISH - -Function UN.onInit -!if ${BUILDUNINST} > 0 - SetShellVarContext ALL - !insertmacro UAC_RunElevated - ${Switch} $0 - ${Case} 0 - ${IfThen} $1 = 1 ${|} Quit ${|} ;we are the outer process, the inner process has done its work, we are done - ${IfThen} $3 <> 0 ${|} ${Break} ${|} ;we are admin, let the show go on - ;fall-through and die - ${Case} 1223 - MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "This uninstaller requires admin privileges, aborting!" - Quit - ${Case} 1062 - MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Logon service not running, aborting!" - Quit - ${Default} - MessageBox mb_IconStop|mb_TopMost|mb_SetForeground "Unable to elevate , error $0" - Quit - ${EndSwitch} -!endif -FunctionEnd - -Section -un.Main -Delete "$smprograms\${APPNAME}.lnk" -Delete "${UNINSTALLER_FULLPATH}" -DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APPNAME}" -RMDir $instdir -SectionEnd -!macroend - - - -/***************************************************/ -!macro CreateUninstaller extractTo mode -!tempfile UNINSTEXE -!system '"${NSISDIR}\MakeNSIS" /DBUILDUNINST=${mode} /DUNINSTEXE=${UNINSTEXE}.exe "${__FILE__}"' = 0 -!system '"${UNINSTEXE}.exe"' = 0 -!system '"windows-dependencies\sign.bat" ${CODESIGN} ${UNINSTEXE}.exe.un' = 0 -File "/oname=${extractTo}" "${UNINSTEXE}.exe.un" -!delfile "${UNINSTEXE}" -!delfile "${UNINSTEXE}.un" -!delfile "${UNINSTEXE}.exe.un" -!undef UNINSTEXE -!macroend - -!ifndef BUILDUNINST - RequestExecutionLevel user - !insertmacro BUILD_INSTALLER -!else - SilentInstall silent - OutFile "${UNINSTEXE}" - !if ${BUILDUNINST} > 0 - RequestExecutionLevel admin - !else - RequestExecutionLevel user - !endif - !insertmacro MUI_PAGE_INSTFILES - !insertmacro BUILD_UNINSTALLER - Section - WriteUninstaller "${UNINSTEXE}.un" - SectionEnd - - Section "Uninstall" - SectionIn RO - Delete "$INSTDIR\${PROJECT_FINAL_NAME}-uber.jar" - Delete "$INSTDIR\${PROJECT_FINAL_NAME}.l4j.ini" - Delete "$INSTDIR\${PROJECT_FINAL_NAME}.exe" - Delete "$INSTDIR\dataloader.ico" - Delete "$INSTDIR\dataloader_uninstall.exe" - RMDir /r "$INSTDIR\licenses" - RMDir /r "$INSTDIR\samples" - RMDir /r "$INSTDIR\bin" - RMDir /r "$SMPROGRAMS\${PROJECT_ORGANIZATION_NAME}\${PROJECT_NAME}" - Delete "$DESKTOP\${PROJECT_NAME}.lnk" - RMDir /r "$APPDATA\${PROJECT_ORGANIZATION_NAME}" - - SectionEnd - -!endif -!insertmacro BUILD_LANGUAGES - - - diff --git a/src/main/assembly/uber.xml b/src/main/assembly/uber.xml deleted file mode 100644 index bcc8c1ed3..000000000 --- a/src/main/assembly/uber.xml +++ /dev/null @@ -1,21 +0,0 @@ - - uber - - jar - - false - - - true - runtime - - - - - ${project.build.outputDirectory} - - - \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/AbstractAction.java b/src/main/java/com/salesforce/dataloader/action/AbstractAction.java index 15fe0adc3..80173394a 100644 --- a/src/main/java/com/salesforce/dataloader/action/AbstractAction.java +++ b/src/main/java/com/salesforce/dataloader/action/AbstractAction.java @@ -27,25 +27,30 @@ package com.salesforce.dataloader.action; import java.util.ArrayList; -import java.util.LinkedList; import java.util.List; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.IVisitor; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataAccessObject; import com.salesforce.dataloader.dao.DataWriter; import com.salesforce.dataloader.dao.csv.CSVFileWriter; +import com.salesforce.dataloader.exception.BatchSizeLimitException; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.ExtractExceptionOnServer; +import com.salesforce.dataloader.exception.HttpClientTransportException; import com.salesforce.dataloader.exception.LoadException; +import com.salesforce.dataloader.exception.LoadExceptionOnServer; import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.exception.OperationException; import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.async.AsyncApiException; import com.sforce.soap.partner.fault.ApiFault; import com.sforce.ws.ConnectionException; @@ -60,17 +65,19 @@ abstract class AbstractAction implements IAction { private final ILoaderProgress monitor; private final Controller controller; - private final IVisitor visitor; + private IVisitor visitor; - private final DataWriter successWriter; - private final DataWriter errorWriter; + protected DataWriter successWriter; + protected DataWriter errorWriter; private final DataAccessObject dao; private final Logger logger; + private boolean enableRetries; + private int maxRetries; protected AbstractAction(Controller controller, ILoaderProgress monitor) throws DataAccessObjectInitializationException { - this.logger = Logger.getLogger(getClass()); + this.logger = DLLogManager.getLogger(getClass()); this.monitor = monitor; this.controller = controller; checkDao(getController().getDao()); @@ -83,6 +90,17 @@ protected AbstractAction(Controller controller, ILoaderProgress monitor) this.errorWriter = null; } this.visitor = createVisitor(); + int retries = -1; + this.enableRetries = controller.getAppConfig().getBoolean(AppConfig.PROP_ENABLE_RETRIES); + if (this.enableRetries) { + try { + // limit the number of max retries in case limit is exceeded + retries = Math.min(AppConfig.MAX_RETRIES_LIMIT, controller.getAppConfig().getInt(AppConfig.PROP_MAX_RETRIES)); + } catch (ParameterLoadException e) { + retries = AppConfig.DEFAULT_MAX_RETRIES; + } + } + this.maxRetries = retries; } /** This method should throw an error if the data access object is configured incorrectly */ @@ -91,8 +109,9 @@ protected AbstractAction(Controller controller, ILoaderProgress monitor) /** @return a new IVisitor object to be used by this action */ protected abstract IVisitor createVisitor(); - /** flushes any remaining records to or from the dao */ - protected abstract void flush() throws OperationException, DataAccessObjectException; + /** flushes any remaining records to or from the dao + * @throws BatchSizeLimitException */ + protected abstract void flush() throws OperationException, DataAccessObjectException, BatchSizeLimitException; /** subclasses should do operation specific initialization here */ protected abstract void initOperation() throws DataAccessObjectInitializationException, OperationException, @@ -117,9 +136,31 @@ protected abstract boolean visit() throws OperationException, DataAccessObjectEx @Override public final void execute() { + List exceptions = null; + for (int numAttempts = 0; numAttempts < this.maxRetries; numAttempts++) { + exceptions = executeOperation(); + boolean doAttemptAgain = false; + if (exceptions != null && exceptions.size() > 0) { + for (Exception e : exceptions) { + if (shouldRetryOperation(e, numAttempts)) { + doAttemptAgain = true; + break; + } + } + } + if (!doAttemptAgain) { + break; // stop the loop + } + } + if (exceptions != null && exceptions.size() > 0) { + exceptions.forEach(this::handleException); + } + } + + private List executeOperation() { List exceptions = new ArrayList<>(); try { - getLogger().info(getMessage("loading", getConfig().getString(Config.OPERATION))); + getLogger().info(getMessage("loading", getConfig().getString(AppConfig.PROP_OPERATION))); getDao().open(); initOperation(); if (writeStatus()) { @@ -129,12 +170,11 @@ public final void execute() { } while (!getMonitor().isCanceled() && visit()) {} - - + } catch (final Exception e) { exceptions.add(e); } finally { - try{ + try { flush(); //make sure we don't early abort here } catch (Exception e) { exceptions.add(e); @@ -145,10 +185,13 @@ public final void execute() { exceptions.add(e); } try { + if (this.errorWriter != null) { + getMonitor().setNumberRowsWithError(this.errorWriter.getCurrentRowNumber()); + } //if no exceptions occurred then display success/error if (exceptions.size() == 0) { final Object[] args = {String.valueOf(getVisitor().getNumberSuccesses()), - getConfig().getString(Config.OPERATION), String.valueOf(getVisitor().getNumberErrors())}; + getConfig().getString(AppConfig.PROP_OPERATION), String.valueOf(getVisitor().getNumberErrors())}; // set the monitor to done if (getMonitor().isCanceled()) { @@ -160,23 +203,64 @@ public final void execute() { } catch (Exception e){ exceptions.add(e); } - //handle each recorded exception - if (exceptions.size() > 0){ - exceptions.forEach(this::handleException); + } + return exceptions; + } + + private boolean shouldRetryOperation(Exception e, int numAttempts) { + if (e instanceof HttpClientTransportException + || e instanceof ExtractExceptionOnServer + || e instanceof LoadExceptionOnServer + || e instanceof ConnectionException) { + if (numAttempts < this.maxRetries-1 && this.enableRetries) { + // loop only if less than MAX_RETRIES + logger.warn("Encountered an error on server when performing " + + controller.getAppConfig().getString(AppConfig.PROP_OPERATION) + + " on attempt " + + numAttempts ); + logger.warn(e.getMessage()); + retrySleep(numAttempts); + return true; } } + return false; + } + + /** + * @param operationName + */ + private void retrySleep(int retryNum) { + int sleepSecs; + try { + sleepSecs = controller.getAppConfig().getInt(AppConfig.PROP_MIN_RETRY_SLEEP_SECS); + } catch (ParameterLoadException e1) { + sleepSecs = AppConfig.DEFAULT_MIN_RETRY_SECS; + } + // sleep between retries is based on the retry attempt #. Sleep for longer periods with each retry + sleepSecs = sleepSecs + (retryNum * 10); // sleep for MIN_RETRY_SLEEP_SECS + 10, 20, 30, etc. + + logger.info(Messages.getFormattedString("Client.retryOperation", + new String[]{Integer.toString(retryNum + 1), + getController().getAppConfig().getString(AppConfig.PROP_OPERATION), + Integer.toString(sleepSecs)})); + try { + Thread.sleep(sleepSecs * 1000); + } catch (InterruptedException e) { // ignore + } } private void closeAll() { getDao().close(); - if (writeStatus()) { - getSuccessWriter().close(); - getErrorWriter().close(); + if (this.successWriter != null) { + this.successWriter.close(); + } + if (this.errorWriter != null) { + this.errorWriter.close(); } } - protected Config getConfig() { - return getController().getConfig(); + protected AppConfig getConfig() { + return getController().getAppConfig(); } protected Controller getController() { @@ -208,9 +292,13 @@ protected DataWriter getSuccessWriter() { return this.successWriter; } - protected IVisitor getVisitor() { + public IVisitor getVisitor() { return this.visitor; } + + protected void setVisitor(IVisitor newVisitor) { + this.visitor = newVisitor; + } protected void handleException(Exception e) { String errMsg = e.getMessage(); @@ -227,54 +315,74 @@ protected void handleException(Exception e) { * @return Error Writer * @throws DataAccessObjectInitializationException */ - private DataWriter createErrorWriter() throws DataAccessObjectInitializationException { - final String filename = getConfig().getString(Config.OUTPUT_ERROR); + public DataWriter createErrorWriter() throws DataAccessObjectInitializationException { + final String filename = getConfig().getString(AppConfig.PROP_OUTPUT_ERROR); if (filename == null || filename.length() == 0) throw new DataAccessObjectInitializationException(getMessage("errorMissingErrorFile")); // TODO: Make sure that specific DAO is not mentioned: use DataReader, DataWriter, or DataAccessObject - return new CSVFileWriter(filename, getConfig()); + this.errorWriter = new CSVFileWriter(filename, getConfig(), AppUtil.COMMA); + return this.errorWriter; } /** * @return Success Writer * @throws DataAccessObjectInitializationException */ - private DataWriter createSuccesWriter() throws DataAccessObjectInitializationException { - final String filename = getConfig().getString(Config.OUTPUT_SUCCESS); + public DataWriter createSuccesWriter() throws DataAccessObjectInitializationException { + final String filename = getConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); if (filename == null || filename.length() == 0) throw new DataAccessObjectInitializationException(getMessage("errorMissingSuccessFile")); // TODO: Make sure that specific DAO is not mentioned: use DataReader, DataWriter, or DataAccessObject - return new CSVFileWriter(filename, getConfig()); + this.successWriter = new CSVFileWriter(filename, getConfig(), AppUtil.COMMA); + return this.successWriter; } - private void openErrorWriter(List headers) throws OperationException { - headers = new LinkedList(headers); + public void openErrorWriter(List headers) throws OperationException { + headers = new ArrayList(headers); + AppConfig appConfig = this.controller.getAppConfig(); - // add the ERROR column - headers.add(Config.ERROR_COLUMN_NAME); + if (appConfig.isBulkV2APIEnabled() + && !appConfig.getString(AppConfig.PROP_OPERATION).equals(OperationInfo.extract.name()) + && !appConfig.getString(AppConfig.PROP_OPERATION).equals(OperationInfo.extract_all.name())) { + headers.add(0, AppConfig.ID_COLUMN_NAME); + headers.add(1, AppConfig.ERROR_COLUMN_NAME); + } else { + // add the ERROR column + headers.add(AppConfig.ERROR_COLUMN_NAME); + } try { getErrorWriter().open(); getErrorWriter().setColumnNames(headers); } catch (final DataAccessObjectInitializationException e) { throw new OperationException( - getMessage("errorOpeningErrorFile", getConfig().getString(Config.OUTPUT_ERROR)), e); + getMessage("errorOpeningErrorFile", getConfig().getString(AppConfig.PROP_OUTPUT_ERROR)), e); } } - private void openSuccessWriter(List headers) throws LoadException { - headers = new LinkedList(headers); + public void openSuccessWriter(List headers) throws LoadException { + headers = new ArrayList(headers); + AppConfig appConfig = this.controller.getAppConfig(); // add the ID column if not there already - if (!Config.ID_COLUMN_NAME.equals(headers.get(0))) { - headers.add(0, Config.ID_COLUMN_NAME); + if (appConfig.isBulkV2APIEnabled() + && !appConfig.getString(AppConfig.PROP_OPERATION).equals(OperationInfo.extract.name()) + && !appConfig.getString(AppConfig.PROP_OPERATION).equals(OperationInfo.extract_all.name())) { + if (headers.size() == 0 || !AppConfig.ID_COLUMN_NAME.equals(headers.get(0))) { + headers.add(0, AppConfig.ID_COLUMN_NAME); + } + headers.add(1, AppConfig.STATUS_COLUMN_NAME_IN_BULKV2); + } else { + if (!AppConfig.ID_COLUMN_NAME.equals(headers.get(0))) { + headers.add(0, AppConfig.ID_COLUMN_NAME); + } + headers.add(AppConfig.STATUS_COLUMN_NAME); } - headers.add(Config.STATUS_COLUMN_NAME); try { getSuccessWriter().open(); getSuccessWriter().setColumnNames(headers); } catch (final DataAccessObjectInitializationException e) { throw new LoadException( - getMessage("errorOpeningSuccessFile", getConfig().getString(Config.OUTPUT_SUCCESS)), e); + getMessage("errorOpeningSuccessFile", getConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS)), e); } } diff --git a/src/main/java/com/salesforce/dataloader/action/AbstractExtractAction.java b/src/main/java/com/salesforce/dataloader/action/AbstractExtractAction.java index 39837d4b8..c1b5902fe 100644 --- a/src/main/java/com/salesforce/dataloader/action/AbstractExtractAction.java +++ b/src/main/java/com/salesforce/dataloader/action/AbstractExtractAction.java @@ -28,16 +28,17 @@ import java.util.*; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.IQueryVisitor; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.*; import com.salesforce.dataloader.exception.*; import com.salesforce.dataloader.mapping.SOQLMapper; +import com.salesforce.dataloader.util.AppUtil; /** * Parent class for all extract dataloader actions. @@ -45,7 +46,7 @@ * @author Colin jarvis * @since 21.0 */ -abstract class AbstractExtractAction extends AbstractAction { +abstract public class AbstractExtractAction extends AbstractAction { protected AbstractExtractAction(Controller controller, ILoaderProgress monitor) throws DataAccessObjectInitializationException { @@ -60,15 +61,15 @@ protected boolean visit() throws OperationException, DataAccessObjectException, @Override protected boolean writeStatus() { - return getConfig().getBoolean(Config.ENABLE_EXTRACT_STATUS_OUTPUT); + return getConfig().getBoolean(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS) && getConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT); } @Override protected void checkDao(DataAccessObject dao) throws DataAccessObjectInitializationException { if (!(dao instanceof DataWriter)) { - final String errMsg = getMessage("errorWrongDao", getConfig().getString(Config.DAO_TYPE), + final String errMsg = getMessage("errorWrongDao", getConfig().getString(AppConfig.PROP_DAO_TYPE), DataAccessObjectFactory.CSV_WRITE_TYPE + " or " + DataAccessObjectFactory.DATABASE_WRITE_TYPE, - getConfig().getString(Config.OPERATION)); + getConfig().getString(AppConfig.PROP_OPERATION)); getLogger().fatal(errMsg); throw new DataAccessObjectInitializationException(errMsg); } @@ -80,12 +81,12 @@ protected DataWriter getDao() { } @Override - protected IQueryVisitor getVisitor() { + public IQueryVisitor getVisitor() { return (IQueryVisitor)super.getVisitor(); } - private List getDaoColumns() { - ((SOQLMapper)getController().getMapper()).initSoqlMapping(getConfig().getString(Config.EXTRACT_SOQL)); + private List getDaoColumnsFromMapper() { + ((SOQLMapper)getController().getMapper()).initSoqlMapping(getConfig().getString(AppConfig.PROP_EXTRACT_SOQL)); return ((SOQLMapper)getController().getMapper()).getDaoColumnsForSoql(); } @@ -99,7 +100,7 @@ static List getColumnsFromSoql(String soql, Logger logger) throws Extrac } // normalize the SOQL string and find the field list - final String trimmedSoql = soql.trim().replaceAll("[\\s]*,[\\s]*", ","); + final String trimmedSoql = soql.trim().replaceAll("[\\s]*,[\\s]*", AppUtil.COMMA); final String upperSOQL = trimmedSoql.toUpperCase(); final int selectPos = upperSOQL.indexOf("SELECT "); if (selectPos == -1) { @@ -112,7 +113,7 @@ static List getColumnsFromSoql(String soql, Logger logger) throws Extrac try { final String fieldString = trimmedSoql.substring(fieldListStart, fieldListEnd).trim(); - final String[] fields = fieldString.split(","); //$NON-NLS-1$ + final String[] fields = fieldString.split(AppUtil.COMMA); //$NON-NLS-1$ return new ArrayList(Arrays.asList(fields)); } catch (final Exception e) { String errMsg; @@ -130,14 +131,20 @@ static List getColumnsFromSoql(String soql, Logger logger) throws Extrac @Override protected List getStatusColumns() throws ExtractException { - return getDaoColumns(); + return getDaoColumnsFromMapper(); } @Override protected void initOperation() throws DataAccessObjectInitializationException, OperationException { - // get columns that will be output from the query and open the outputs - final List daoColumns = getDaoColumns(); - getDao().setColumnNames(daoColumns); + ((SOQLMapper)getController().getMapper()).clearMappings(); + if (getController().getAppConfig().getBoolean(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS)) { + final List daoColumns = getDaoColumnsFromMapper(); + getDao().setColumnNames(daoColumns); + } else { + // check for syntactical correctness and presence of nested soql. + // nested soql is currently not supported. + ((SOQLMapper)getController().getMapper()).parseSoql(getConfig().getString(AppConfig.PROP_EXTRACT_SOQL)); + } } @Override diff --git a/src/main/java/com/salesforce/dataloader/action/AbstractLoadAction.java b/src/main/java/com/salesforce/dataloader/action/AbstractLoadAction.java index 2256c7ce4..f36ff2ddd 100644 --- a/src/main/java/com/salesforce/dataloader/action/AbstractLoadAction.java +++ b/src/main/java/com/salesforce/dataloader/action/AbstractLoadAction.java @@ -28,18 +28,19 @@ import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataAccessObject; import com.salesforce.dataloader.dao.DataAccessObjectFactory; import com.salesforce.dataloader.dao.DataReader; +import com.salesforce.dataloader.exception.BatchSizeLimitException; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.exception.OperationException; import com.salesforce.dataloader.exception.ParameterLoadException; import com.salesforce.dataloader.mapping.LoadMapper; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import com.salesforce.dataloader.util.DAORowUtil; import com.sforce.ws.ConnectionException; @@ -50,7 +51,7 @@ * @since 6.0 */ abstract class AbstractLoadAction extends AbstractAction { - + protected AbstractLoadAction(Controller controller, ILoaderProgress monitor) throws DataAccessObjectInitializationException { super(controller, monitor); @@ -62,9 +63,9 @@ protected AbstractLoadAction(Controller controller, ILoaderProgress monitor) @Override protected void checkDao(DataAccessObject dao) throws DataAccessObjectInitializationException { if (!(dao instanceof DataReader)) { - final String errMsg = getMessage("errorWrongDao", getConfig().getString(Config.DAO_TYPE), + final String errMsg = getMessage("errorWrongDao", getConfig().getString(AppConfig.PROP_DAO_TYPE), DataAccessObjectFactory.CSV_READ_TYPE + " or " + DataAccessObjectFactory.DATABASE_READ_TYPE, - getConfig().getString(Config.OPERATION)); + getConfig().getString(AppConfig.PROP_OPERATION)); getLogger().fatal(errMsg); throw new DataAccessObjectInitializationException(errMsg); } @@ -77,18 +78,40 @@ protected void checkDao(DataAccessObject dao) throws DataAccessObjectInitializat protected boolean visit() throws DataAccessObjectException, ParameterLoadException, OperationException, ConnectionException { - final int loadBatchSize = this.getConfig().getLoadBatchSize(); - final List daoRowList = getDao().readRowList(loadBatchSize); + final int loadBatchSize = this.getConfig().getMaxRowsInImportBatch(); + final int daoRowNumBase = getDao().getCurrentRowNumber(); + final List daoRowList = getDao().readTableRowList(loadBatchSize); if (daoRowList == null || daoRowList.size() == 0) return false; - for (final Row daoRow : daoRowList) { - if (!DAORowUtil.isValidRow(daoRow)) return false; - getVisitor().visit(daoRow); + int daoRowCount = 0; + + for (final TableRow daoRow : daoRowList) { + if (!DAORowUtil.isValidTableRow(daoRow)) { + getVisitor().setRowConversionStatus(daoRowNumBase + daoRowCount++, + false); + return false; + } + boolean successfulVisit = false; + try { + successfulVisit = getVisitor().visit(daoRow); + } catch (BatchSizeLimitException ex) { + // retry the same row again + try { + if (this.getConfig().isBulkV2APIEnabled()) { + // create a new visitor for a new job + setVisitor(this.createVisitor()); + } + successfulVisit = getVisitor().visit(daoRow); + } catch (BatchSizeLimitException e) { + getLogger().warn("row byte size is too large to process"); + } + } + getVisitor().setRowConversionStatus(daoRowNumBase + daoRowCount++, successfulVisit); } return true; } @Override - protected void flush() throws OperationException, DataAccessObjectException { + protected void flush() throws OperationException, DataAccessObjectException, BatchSizeLimitException { getVisitor().flushRemaining(); } @@ -97,7 +120,7 @@ protected void initOperation() throws MappingInitializationException, DataAccess // ensure all field mappings are valid before data load ((LoadMapper)this.getController().getMapper()).verifyMappingsAreValid(); // start the Progress Monitor - getMonitor().beginTask(getMessage("loading", getConfig().getString(Config.OPERATION)), getDao().getTotalRows()); + getMonitor().beginTask(getMessage("loading", getConfig().getString(AppConfig.PROP_OPERATION)), getDao().getTotalRows()); // set the starting row DAORowUtil.get().skipRowToStartOffset(getConfig(), getDao(), getMonitor(), !getConfig().isBulkAPIEnabled()); } @@ -113,7 +136,7 @@ protected DataReader getDao() { } @Override - protected DAOLoadVisitor getVisitor() { + public DAOLoadVisitor getVisitor() { return (DAOLoadVisitor)super.getVisitor(); } diff --git a/src/main/java/com/salesforce/dataloader/action/BulkExtractAction.java b/src/main/java/com/salesforce/dataloader/action/BulkExtractAction.java index 2bfb5855b..b7f9e3f7e 100644 --- a/src/main/java/com/salesforce/dataloader/action/BulkExtractAction.java +++ b/src/main/java/com/salesforce/dataloader/action/BulkExtractAction.java @@ -27,8 +27,9 @@ package com.salesforce.dataloader.action; import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.action.visitor.BulkQueryVisitor; import com.salesforce.dataloader.action.visitor.IVisitor; +import com.salesforce.dataloader.action.visitor.bulk.BulkV1QueryVisitor; +import com.salesforce.dataloader.action.visitor.bulk.BulkV2QueryVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; @@ -47,7 +48,10 @@ public BulkExtractAction(Controller controller, ILoaderProgress monitor) @Override protected IVisitor createVisitor() { - return new BulkQueryVisitor(getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); + if (getController().getAppConfig().isBulkV2APIEnabled() ) { + return new BulkV2QueryVisitor(this, getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); + } + return new BulkV1QueryVisitor(this, getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); } } diff --git a/src/main/java/com/salesforce/dataloader/action/BulkLoadAction.java b/src/main/java/com/salesforce/dataloader/action/BulkLoadAction.java index 1291d0190..5c821aacc 100644 --- a/src/main/java/com/salesforce/dataloader/action/BulkLoadAction.java +++ b/src/main/java/com/salesforce/dataloader/action/BulkLoadAction.java @@ -26,11 +26,14 @@ package com.salesforce.dataloader.action; +import java.util.List; + import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.action.visitor.BulkLoadVisitor; import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.action.visitor.bulk.BulkLoadVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.mapping.LoadMapper; /** * @author Jesper Joergensen, Colin Jarvis @@ -47,5 +50,27 @@ public BulkLoadAction(Controller controller, ILoaderProgress monitor) protected DAOLoadVisitor createVisitor() { return new BulkLoadVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); } + + @Override + protected List getStatusColumns() { + if (this.getConfig().isBulkV2APIEnabled()) { + // Success and error results in Bulk v2 job are downloaded separately. + // + // Success and error results in Bulk V2 upload operations contain all of the mapped columns + // in the order in which they were uploaded. + // Header of the error results file contains mapped DAO columns, not all DAO columns. + // Header of the success results file is saved as-is from the downloaded success file. It + // contains server-side field names along with a column answering "yes" or "no" to the header + // field "created?" and another column labeled "sf__id" listing id of the sobject. + LoadMapper mapper = (LoadMapper)this.getController().getMapper(); + return mapper.getMappedDaoColumns(); + } else { + // A single file that matches rows in uploaded batch contains success and error + // results in Bulk v1. + // Each row has 2 columns: "ID" and "STATUS". + // Header row of the success and error files can be generated using DAO's header row. + return super.getStatusColumns(); + } + } -} +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/DeleteAction.java b/src/main/java/com/salesforce/dataloader/action/DeleteAction.java index b9baa9e4a..cd1c989e9 100644 --- a/src/main/java/com/salesforce/dataloader/action/DeleteAction.java +++ b/src/main/java/com/salesforce/dataloader/action/DeleteAction.java @@ -29,7 +29,9 @@ import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; -import com.salesforce.dataloader.action.visitor.DeleteVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerDeleteVisitor; +import com.salesforce.dataloader.action.visitor.rest.RESTDeleteVisitor; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; @@ -44,7 +46,12 @@ public DeleteAction(Controller controller, ILoaderProgress monitor) throws DataA @Override protected DAOLoadVisitor createVisitor() { - return new DeleteVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + if (getController().getAppConfig().isRESTAPIEnabled() + && getController().getAppConfig().getBoolean(AppConfig.PROP_DELETE_WITH_EXTERNALID)) { + return new RESTDeleteVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + } else { + return new PartnerDeleteVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + } } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/IAction.java b/src/main/java/com/salesforce/dataloader/action/IAction.java index 9f642c177..972cdd8fb 100644 --- a/src/main/java/com/salesforce/dataloader/action/IAction.java +++ b/src/main/java/com/salesforce/dataloader/action/IAction.java @@ -26,6 +26,7 @@ package com.salesforce.dataloader.action; +import com.salesforce.dataloader.action.visitor.IVisitor; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.OperationException; @@ -41,4 +42,5 @@ public interface IAction { * @throws DataAccessObjectException */ public void execute() throws DataAccessObjectException, OperationException; + public IVisitor getVisitor(); } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/InsertAction.java b/src/main/java/com/salesforce/dataloader/action/InsertAction.java index 7f96b0e50..c64c5543b 100644 --- a/src/main/java/com/salesforce/dataloader/action/InsertAction.java +++ b/src/main/java/com/salesforce/dataloader/action/InsertAction.java @@ -28,7 +28,7 @@ import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; -import com.salesforce.dataloader.action.visitor.InsertVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerInsertVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; @@ -44,7 +44,7 @@ public InsertAction(Controller controller, ILoaderProgress monitor) throws DataA @Override protected DAOLoadVisitor createVisitor() { - return new InsertVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + return new PartnerInsertVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); } } diff --git a/src/main/java/com/salesforce/dataloader/action/OperationInfo.java b/src/main/java/com/salesforce/dataloader/action/OperationInfo.java index 1468f99d8..8dc3a20c8 100644 --- a/src/main/java/com/salesforce/dataloader/action/OperationInfo.java +++ b/src/main/java/com/salesforce/dataloader/action/OperationInfo.java @@ -25,24 +25,15 @@ */ package com.salesforce.dataloader.action; -import org.apache.log4j.Logger; -import org.eclipse.jface.dialogs.IDialogConstants; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.swt.graphics.Image; import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.ui.*; -import com.salesforce.dataloader.ui.LoadWizard.DeleteWizard; -import com.salesforce.dataloader.ui.LoadWizard.HardDeleteWizard; -import com.salesforce.dataloader.ui.LoadWizard.InsertWizard; -import com.salesforce.dataloader.ui.LoadWizard.UpdateWizard; -import com.salesforce.dataloader.ui.LoadWizard.UpsertWizard; -import com.salesforce.dataloader.ui.extraction.ExtractAllWizard; -import com.salesforce.dataloader.ui.extraction.ExtractionWizard; import com.salesforce.dataloader.ui.uiActions.OperationUIAction; import com.sforce.async.OperationEnum; @@ -53,39 +44,42 @@ */ public enum OperationInfo { - insert(InsertAction.class, InsertWizard.class), - update(UpdateAction.class, UpdateWizard.class), - upsert(UpsertAction.class, UpsertWizard.class), - delete(DeleteAction.class, DeleteWizard.class), - hard_delete(null, BulkLoadAction.class, HardDeleteWizard.class), - extract(PartnerExtractAction.class, BulkExtractAction.class, ExtractionWizard.class), - extract_all(PartnerExtractAllAction.class, null, ExtractAllWizard.class); + insert(InsertAction.class, BulkLoadAction.class, BulkLoadAction.class, OperationInfoUIHelper.insert), + update(UpdateAction.class, BulkLoadAction.class, BulkLoadAction.class, OperationInfoUIHelper.update), + upsert(UpsertAction.class, BulkLoadAction.class, BulkLoadAction.class, OperationInfoUIHelper.upsert), + delete(DeleteAction.class, BulkLoadAction.class, BulkLoadAction.class, OperationInfoUIHelper.delete), + undelete(UndeleteAction.class, null, null, OperationInfoUIHelper.undelete), + hard_delete(null, BulkLoadAction.class, BulkLoadAction.class, OperationInfoUIHelper.hard_delete), + extract(PartnerExtractAction.class, BulkExtractAction.class, BulkExtractAction.class, OperationInfoUIHelper.extract), + extract_all(PartnerExtractAllAction.class, BulkExtractAction.class, BulkExtractAction.class, OperationInfoUIHelper.extract_all); /** all operations, in order */ - public static final OperationInfo[] ALL_OPERATIONS_IN_ORDER = { insert, update, upsert, delete, hard_delete, - extract, extract_all }; + public static final OperationInfo[] ALL_OPERATIONS_IN_ORDER = + { insert, update, upsert, delete, undelete, hard_delete, extract, extract_all }; - private static final Logger logger = Logger.getLogger(OperationInfo.class); + private static final Logger logger = DLLogManager.getLogger(OperationInfo.class); private final Class partnerAPIActionClass; private final Class bulkAPIActionClass; - private final Class wizardClass; + private final Class bulkV2APIActionClass; + private final OperationInfoUIHelper uiHelper; - private OperationInfo(Class partnerAPIActionClass, Class bulkAPIActionClass, - Class wizardClass) { + private OperationInfo(Class partnerAPIActionClass, Class bulkAPIActionClass, Class bulkV2APIActionClass, + OperationInfoUIHelper uiHelper) { this.partnerAPIActionClass = partnerAPIActionClass; this.bulkAPIActionClass = bulkAPIActionClass; - this.wizardClass = wizardClass; - } - - private OperationInfo(Class partnerAPIActionClass, Class wizardClass) { - this(partnerAPIActionClass, BulkLoadAction.class, wizardClass); + this.bulkV2APIActionClass = bulkAPIActionClass; + this.uiHelper = uiHelper; } public boolean bulkAPIEnabled() { return this.bulkAPIActionClass != null; } + + public boolean bulkV2APIEnabled() { + return this.bulkV2APIActionClass != null; + } public boolean partnerAPIEnabled() { return this.partnerAPIActionClass != null; @@ -93,8 +87,12 @@ public boolean partnerAPIEnabled() { public IAction instantiateAction(Controller ctl, ILoaderProgress loaderProgress) { logger.info(Messages.getMessage(getClass(), "createAction", this)); - final Class cls = ctl.getConfig().isBulkAPIEnabled() && bulkAPIEnabled() ? this.bulkAPIActionClass - : this.partnerAPIActionClass; + Class cls = this.partnerAPIActionClass; + if (ctl.getAppConfig().isBulkAPIEnabled() && bulkAPIEnabled()) { + cls = this.bulkAPIActionClass; + } else if (ctl.getAppConfig().isBulkV2APIEnabled() && bulkV2APIEnabled()) { + cls = this.bulkV2APIActionClass; + } try { return cls.getConstructor(Controller.class, ILoaderProgress.class).newInstance(ctl, loaderProgress); } catch (Exception e) { @@ -110,49 +108,45 @@ private RuntimeException unsupportedInstantiation(Exception e, Class cls) { } public String getIconName() { - if (this == hard_delete) return delete.getIconName(); - if (this == upsert) return update.getIconName(); - if (this == extract_all) return extract.getIconName(); - return name() + "_icon"; + return this.uiHelper.getIconName(); } public String getIconLocation() { - if (this == hard_delete) return delete.getIconLocation(); - if (this == extract_all) return extract.getIconLocation(); - return "img/icons/icon_" + name() + ".gif"; + return this.uiHelper.getIconLocation(); } public String getMenuLabel() { - return Labels.getString(name() + ".UIAction.menuText"); + return this.uiHelper.getMenuLabel(); } public String getToolTipText() { - return Labels.getString(name() + ".UIAction.tooltipText"); + return this.uiHelper.getToolTipText(); } public String getLabel() { - return Labels.getString("UI." + name()); - } - - public Wizard instantiateWizard(Controller ctl) { - logger.info(Messages.getMessage(getClass(), "creatingWizard", this)); - try { - return ((Class)this.wizardClass).getConstructor(Controller.class).newInstance(ctl); - } catch (Exception e) { - throw unsupportedInstantiation(e, this.wizardClass); - } + return this.uiHelper.getLabel(); } public OperationUIAction createUIAction(Controller ctl) { return new OperationUIAction(ctl, this); } - public OperationEnum getOperationEnum() { + public OperationEnum getBulkOperationEnum() { switch (this) { + case insert: + return OperationEnum.insert; + case update: + return OperationEnum.update; + case upsert: + return OperationEnum.upsert; + case delete: + return OperationEnum.delete; case hard_delete: return OperationEnum.hardDelete; case extract: return OperationEnum.query; + case extract_all: + return OperationEnum.queryAll; default: return OperationEnum.valueOf(name()); } @@ -161,37 +155,38 @@ public OperationEnum getOperationEnum() { public boolean isDelete() { return this == delete || this == hard_delete; } - - public int getDialogIdx() { - return IDialogConstants.CLIENT_ID + ordinal() + 1; + + public boolean isUndelete() { + return this == undelete; } - - public boolean isOperationAllowed(Config cfg) { - // all operations are always allowed except hard delete, which requires bulk api - return this != hard_delete || cfg.isBulkAPIEnabled(); + + public int getDialogIdx() { + return this.uiHelper.getDialogIdx(); } - public Image getIconImage() { - Image result = UIUtils.getImageRegistry().get(getIconName()); - if (result == null) { throw new NullPointerException(name() + ": cannot find image: " + getIconName() + ", " - + getIconLocation()); } - return result; + public boolean isOperationAllowed(AppConfig cfg) { + if (cfg.isBulkAPIEnabled()) { + return this.bulkAPIActionClass != null; + } else if (cfg.isBulkV2APIEnabled()) { + return this.bulkV2APIActionClass != null; + } + return this.partnerAPIActionClass != null; } public ImageDescriptor getIconImageDescriptor() { - ImageDescriptor result = UIUtils.getImageRegistry().getDescriptor(getIconName()); - if (result == null) { throw new NullPointerException(name() + ": cannot find image descriptor: " - + getIconName() + ", " + getIconLocation()); } - return result; + return this.uiHelper.getIconImageDescriptor(); } public String getInfoMessageForDataSelectionPage() { - if (this == hard_delete) return Labels.getString("DataSelectionPage." + name()); - return null; + return this.uiHelper.getInfoMessageForDataSelectionPage(); } public boolean isExtraction() { return this == extract || this == extract_all; } + + public OperationInfoUIHelper getUIHelper() { + return this.uiHelper; + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/OperationInfoUIHelper.java b/src/main/java/com/salesforce/dataloader/action/OperationInfoUIHelper.java new file mode 100644 index 000000000..c737ad9ed --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/OperationInfoUIHelper.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.action; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.swt.graphics.Image; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.ui.*; +import com.salesforce.dataloader.ui.LoadWizard.DeleteWizard; +import com.salesforce.dataloader.ui.LoadWizard.HardDeleteWizard; +import com.salesforce.dataloader.ui.LoadWizard.InsertWizard; +import com.salesforce.dataloader.ui.LoadWizard.UndeleteWizard; +import com.salesforce.dataloader.ui.LoadWizard.UpdateWizard; +import com.salesforce.dataloader.ui.LoadWizard.UpsertWizard; +import com.salesforce.dataloader.ui.extraction.ExtractAllWizard; +import com.salesforce.dataloader.ui.extraction.ExtractionWizard; +import com.salesforce.dataloader.ui.uiActions.OperationUIAction; +import com.sforce.async.OperationEnum; + +/** + * Enum containing data and utility methods for data loader operations. + * + * @author Colin Jarvis + */ +public enum OperationInfoUIHelper { + + insert(InsertWizard.class), + update(UpdateWizard.class), + upsert(UpsertWizard.class), + delete(DeleteWizard.class), + undelete(UndeleteWizard.class), + hard_delete(HardDeleteWizard.class), + extract(ExtractionWizard.class), + extract_all(ExtractAllWizard.class); + + /** all operations, in order */ + public static final OperationInfoUIHelper[] ALL_OPERATIONS_IN_ORDER = { insert, update, upsert, delete, hard_delete, + extract, extract_all }; + + private static final Logger logger = DLLogManager.getLogger(OperationInfoUIHelper.class); + + private final Class wizardClass; + + private OperationInfoUIHelper(Class wizardClass) { + this.wizardClass = wizardClass; + } + + private RuntimeException unsupportedInstantiation(Exception e, Class cls) { + final String message = Messages + .getMessage(getClass(), "errorOperationInstantiation", this, String.valueOf(cls)); + logger.fatal(message); + return new UnsupportedOperationException(message, e); + } + + public String getIconName() { + if (this == hard_delete) return delete.getIconName(); + if (this == upsert) return update.getIconName(); + if (this == undelete) return update.getIconName(); + if (this == extract_all) return extract.getIconName(); + return name() + "_icon"; + } + + public String getIconLocation() { + if (this == hard_delete) return delete.getIconLocation(); + if (this == extract_all) return extract.getIconLocation(); + return "img/icons/icon_" + name() + ".gif"; + } + + public String getMenuLabel() { + return Labels.getString(name() + ".UIAction.menuText"); + } + + public String getToolTipText() { + return Labels.getString(name() + ".UIAction.tooltipText"); + } + + public String getLabel() { + return Labels.getString("UI." + name()); + } + + public Wizard instantiateWizard(Controller ctl) { + logger.info(Messages.getMessage(getClass(), "creatingWizard", this)); + try { + return ((Class)this.wizardClass).getConstructor(Controller.class).newInstance(ctl); + } catch (Exception e) { + throw unsupportedInstantiation(e, this.wizardClass); + } + } + + public OperationUIAction createUIAction(Controller ctl, OperationInfo operation) { + return new OperationUIAction(ctl, operation); + } + + public OperationEnum getOperationEnum() { + switch (this) { + case hard_delete: + return OperationEnum.hardDelete; + case extract: + return OperationEnum.query; + default: + return OperationEnum.valueOf(name()); + } + } + + public boolean isDelete() { + return this == delete || this == hard_delete; + } + + public int getDialogIdx() { + return IDialogConstants.CLIENT_ID + ordinal() + 1; + } + + public boolean isOperationAllowed(AppConfig cfg) { + // all operations are always allowed except hard delete, which requires bulk api + return this != hard_delete || cfg.isBulkAPIEnabled() || cfg.isBulkV2APIEnabled(); + } + + public Image getIconImage() { + Image result = UIUtils.getImageRegistry().get(getIconName()); + if (result == null) { throw new NullPointerException(name() + ": cannot find image: " + getIconName() + ", " + + getIconLocation()); } + return result; + } + + public ImageDescriptor getIconImageDescriptor() { + ImageDescriptor result = UIUtils.getImageRegistry().getDescriptor(getIconName()); + if (result == null) { throw new NullPointerException(name() + ": cannot find image descriptor: " + + getIconName() + ", " + getIconLocation()); } + return result; + } + + public String getInfoMessageForDataSelectionPage() { + if (this == hard_delete) return Labels.getString("DataSelectionPage." + name()); + return null; + } + + public boolean isExtraction() { + return this == extract || this == extract_all; + } + +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/PartnerExtractAction.java b/src/main/java/com/salesforce/dataloader/action/PartnerExtractAction.java index 30e551e7d..43ae63863 100644 --- a/src/main/java/com/salesforce/dataloader/action/PartnerExtractAction.java +++ b/src/main/java/com/salesforce/dataloader/action/PartnerExtractAction.java @@ -28,7 +28,7 @@ import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.IQueryVisitor; -import com.salesforce.dataloader.action.visitor.PartnerQueryVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerQueryVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; @@ -45,7 +45,7 @@ public PartnerExtractAction(Controller controller, ILoaderProgress monitor) @Override protected IQueryVisitor createVisitor() { - return new PartnerQueryVisitor(getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); + return new PartnerQueryVisitor(this, getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); } } diff --git a/src/main/java/com/salesforce/dataloader/action/PartnerExtractAllAction.java b/src/main/java/com/salesforce/dataloader/action/PartnerExtractAllAction.java index fb6042fe0..cc4ee1729 100644 --- a/src/main/java/com/salesforce/dataloader/action/PartnerExtractAllAction.java +++ b/src/main/java/com/salesforce/dataloader/action/PartnerExtractAllAction.java @@ -28,7 +28,7 @@ import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.IVisitor; -import com.salesforce.dataloader.action.visitor.PartnerQueryAllVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerQueryAllVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; @@ -47,7 +47,7 @@ public PartnerExtractAllAction(Controller controller, ILoaderProgress monitor) @Override protected IVisitor createVisitor() { - return new PartnerQueryAllVisitor(getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); + return new PartnerQueryAllVisitor(this, getController(), getMonitor(), getDao(), getSuccessWriter(), getErrorWriter()); } } diff --git a/src/main/java/com/salesforce/dataloader/action/UndeleteAction.java b/src/main/java/com/salesforce/dataloader/action/UndeleteAction.java new file mode 100644 index 000000000..eb63f9233 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/UndeleteAction.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action; + + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerUndeleteVisitor; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.exception.DataAccessObjectException; + +/** + * @author Lexi Viripaeff + */ +class UndeleteAction extends AbstractLoadAction { + + public UndeleteAction(Controller controller, ILoaderProgress monitor) throws DataAccessObjectException { + super(controller, monitor); + } + + @Override + protected DAOLoadVisitor createVisitor() { + return new PartnerUndeleteVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + } + +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/UpdateAction.java b/src/main/java/com/salesforce/dataloader/action/UpdateAction.java index 884f1b542..bf358c791 100644 --- a/src/main/java/com/salesforce/dataloader/action/UpdateAction.java +++ b/src/main/java/com/salesforce/dataloader/action/UpdateAction.java @@ -23,12 +23,12 @@ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ - package com.salesforce.dataloader.action; import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; -import com.salesforce.dataloader.action.visitor.UpdateVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerUpdateVisitor; +import com.salesforce.dataloader.action.visitor.rest.RESTUpdateVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; @@ -43,7 +43,11 @@ public UpdateAction(Controller controller, ILoaderProgress monitor) throws DataA @Override protected DAOLoadVisitor createVisitor() { - return new UpdateVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + if (getController().getAppConfig().isRESTAPIEnabled()) { + return new RESTUpdateVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + } else { + return new PartnerUpdateVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + } } } diff --git a/src/main/java/com/salesforce/dataloader/action/UpsertAction.java b/src/main/java/com/salesforce/dataloader/action/UpsertAction.java index 77360d0b9..da8fc8209 100644 --- a/src/main/java/com/salesforce/dataloader/action/UpsertAction.java +++ b/src/main/java/com/salesforce/dataloader/action/UpsertAction.java @@ -28,7 +28,7 @@ package com.salesforce.dataloader.action; import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.action.visitor.UpsertVisitor; +import com.salesforce.dataloader.action.visitor.partner.PartnerUpsertVisitor; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; @@ -43,8 +43,8 @@ public UpsertAction(Controller controller, ILoaderProgress monitor) throws DataA } @Override - protected UpsertVisitor createVisitor() { - return new UpsertVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); + protected PartnerUpsertVisitor createVisitor() { + return new PartnerUpsertVisitor(getController(), getMonitor(), getSuccessWriter(), getErrorWriter()); } } diff --git a/src/main/java/com/salesforce/dataloader/action/progress/ILoaderProgress.java b/src/main/java/com/salesforce/dataloader/action/progress/ILoaderProgress.java index bd2746881..24a3bb41e 100644 --- a/src/main/java/com/salesforce/dataloader/action/progress/ILoaderProgress.java +++ b/src/main/java/com/salesforce/dataloader/action/progress/ILoaderProgress.java @@ -34,5 +34,10 @@ public interface ILoaderProgress { void worked(int worked); void setSubTask(String name); boolean isCanceled(); + boolean isSuccess(); + String getMessage(); void setNumberBatchesTotal(int numberBatchesTotal); + int getNumberBatchesTotal(); + void setNumberRowsWithError(int rowsWithError); + int getNumberRowsWithError(); } diff --git a/src/main/java/com/salesforce/dataloader/action/progress/NihilistProgressAdapter.java b/src/main/java/com/salesforce/dataloader/action/progress/NihilistProgressAdapter.java index f3dec670b..dddbcb6b0 100644 --- a/src/main/java/com/salesforce/dataloader/action/progress/NihilistProgressAdapter.java +++ b/src/main/java/com/salesforce/dataloader/action/progress/NihilistProgressAdapter.java @@ -26,9 +26,6 @@ package com.salesforce.dataloader.action.progress; -import org.apache.log4j.Logger; - - /** * This class implements the ILoaderProgress but does nothing with * the callbacks. @@ -38,54 +35,93 @@ * @author Lexi Viripaeff * @since 6.0 */ -public enum NihilistProgressAdapter implements ILoaderProgress { - INSTANCE; - public static NihilistProgressAdapter get() { - return INSTANCE; - } +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; - //logger - private final Logger logger = Logger.getLogger(getClass()); +import org.apache.logging.log4j.Logger; - @Override - public void beginTask(String name, int totalWork) { +import com.salesforce.dataloader.util.DLLogManager; +public class NihilistProgressAdapter implements ILoaderProgress { + private String message; + private boolean success = false; + private int numRowsWithError = 0; + private int numberBatchesTotal = 0; + private final Logger logger = DLLogManager.getLogger(getClass()); + private int workDone; + private int totalWork; + private final List subTasksInOrder = new ArrayList(); + + + public NihilistProgressAdapter() { + // no op } - @Override - public void doneError(String msgs) { - logger.error(msgs); + public void beginTask(String name, int totalWork) { + this.totalWork = totalWork; + } + + public void doneError(String msg) { + success = false; + message = msg; + logger.error(msg); } - @Override public void doneSuccess(String msg) { + success = true; + message = msg; logger.info(msg); - } - @Override public void worked(int worked) { - + this.workDone += worked; } - public void setTaskName(String name) { + public void setSubTask(String name) { + this.subTasksInOrder.add(name); + logger.info(name); + } + public int getTotalWork() { + return this.totalWork; + } + + public int getNumWorked() { + return this.workDone; } - @Override - public void setSubTask(String name) { - logger.info(name); + public List getSubTasks() { + return Collections.unmodifiableList(this.subTasksInOrder); } - @Override public boolean isCanceled() { return false; } - @Override public void setNumberBatchesTotal(int numberBatchesTotal) { - // nothing + this.numberBatchesTotal = numberBatchesTotal; + } + + public boolean isSuccess() { + return this.success; + } + + public String getMessage() { + return this.message; + } + + public int getNumberBatchesTotal() { + return this.numberBatchesTotal; } -} + public void setNumberRowsWithError(int rowsWithError) { + this.numRowsWithError = rowsWithError; + + } + + public int getNumberRowsWithError() { + return this.numRowsWithError; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/progress/SWTProgressAdapter.java b/src/main/java/com/salesforce/dataloader/action/progress/SWTProgressAdapter.java index 19f0360b4..e3cc65d86 100644 --- a/src/main/java/com/salesforce/dataloader/action/progress/SWTProgressAdapter.java +++ b/src/main/java/com/salesforce/dataloader/action/progress/SWTProgressAdapter.java @@ -37,13 +37,13 @@ /** * @author Lexi Viripaeff */ -public class SWTProgressAdapter implements ILoaderProgress { +public class SWTProgressAdapter extends NihilistProgressAdapter { private IProgressMonitor monitor = null; - private String dispMessage; private final Controller controller; public SWTProgressAdapter(IProgressMonitor monitor_, Controller controller) { + super(); monitor = monitor_; this.controller = controller; } @@ -53,13 +53,10 @@ public SWTProgressAdapter(IProgressMonitor monitor_, Controller controller) { */ @Override public void beginTask(String name, int totalWork) { + super.beginTask(name, totalWork); monitor.beginTask(name, totalWork); } - - public void done() { - monitor.done(); - } - + /* * (non-Javadoc) * @@ -67,21 +64,21 @@ public void done() { */ @Override public void doneSuccess(String message) { + super.doneSuccess(message); monitor.done(); - - dispMessage = message; + controller.setLastOperationSuccessful(true); Display.getDefault().syncExec(new Thread() { @Override public void run() { //if extraction pop open an extraction finished dialog - if (controller.getConfig().getOperationInfo().isExtraction()) { + if (controller.getAppConfig().getOperationInfo().isExtraction()) { ExtractionFinishDialog dlg = new ExtractionFinishDialog(LoaderWindow.getApp().getShell(), controller); - dlg.setMessage(dispMessage); + dlg.setMessage(getMessage()); dlg.open(); } else { LoadFinishDialog dlg = new LoadFinishDialog(LoaderWindow.getApp().getShell(), controller); - dlg.setMessage(dispMessage); + dlg.setMessage(getMessage()); dlg.open(); } } @@ -91,12 +88,13 @@ public void run() { @Override public void doneError(String message) { + super.doneError(message); monitor.done(); - dispMessage = message; + controller.setLastOperationSuccessful(false); Display.getDefault().syncExec(new Thread() { @Override public void run() { - UIUtils.errorMessageBox(LoaderWindow.getApp().getShell(), dispMessage); + UIUtils.errorMessageBox(LoaderWindow.getApp().getShell(), getMessage()); } }); } @@ -108,18 +106,10 @@ public void run() { */ @Override public void worked(int worked) { + super.worked(worked); monitor.worked(worked); } - /* - * (non-Javadoc) - * - * @see com.sfdc.action.progress.ILoaderProgress#setTaskName(java.lang.String) - */ - public void setTaskName(String name) { - monitor.setTaskName(name); - } - /* * (non-Javadoc) * @@ -127,6 +117,7 @@ public void setTaskName(String name) { */ @Override public void setSubTask(String name) { + super.setSubTask(name); monitor.subTask(name); } @@ -139,10 +130,4 @@ public void setSubTask(String name) { public boolean isCanceled() { return monitor.isCanceled(); } - - @Override - public void setNumberBatchesTotal(int numberBatchesTotal) { - // nothing - } - } diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/AbstractQueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/AbstractQueryVisitor.java index d68ebd4d3..021e49254 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/AbstractQueryVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/AbstractQueryVisitor.java @@ -26,8 +26,10 @@ package com.salesforce.dataloader.action.visitor; +import com.salesforce.dataloader.action.AbstractExtractAction; import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataWriter; @@ -38,13 +40,23 @@ import com.salesforce.dataloader.exception.ParameterLoadException; import com.salesforce.dataloader.mapping.SOQLMapper; import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableHeader; import com.sforce.async.AsyncApiException; import com.sforce.soap.partner.fault.ApiFault; import com.sforce.ws.ConnectionException; +import java.io.InputStream; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Base64; import java.util.Iterator; -import java.util.LinkedList; +import java.util.ArrayList; import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; /** * Superclass for all query visitors @@ -52,22 +64,25 @@ * @author Colin Jarvis * @since 21.0 */ -abstract class AbstractQueryVisitor extends AbstractVisitor implements IQueryVisitor { +public abstract class AbstractQueryVisitor extends AbstractVisitor implements IQueryVisitor { private final DataWriter queryWriter; private final String soql; private final List batchRows; private final List batchIds; private final int batchSize; + protected final AbstractExtractAction action; + private static final Logger logger = DLLogManager.getLogger(AbstractQueryVisitor.class); - public AbstractQueryVisitor(Controller controller, ILoaderProgress monitor, DataWriter queryWriter, + public AbstractQueryVisitor(AbstractExtractAction action, Controller controller, ILoaderProgress monitor, DataWriter queryWriter, DataWriter successWriter, DataWriter errorWriter) { super(controller, monitor, successWriter, errorWriter); this.queryWriter = queryWriter; - this.soql = getConfig().getString(Config.EXTRACT_SOQL); - this.batchRows = new LinkedList(); - this.batchIds = new LinkedList(); + this.soql = getConfig().getString(AppConfig.PROP_EXTRACT_SOQL); + this.batchRows = new ArrayList(); + this.batchIds = new ArrayList(); this.batchSize = getWriteBatchSize(); + this.action = action; } @Override @@ -99,7 +114,7 @@ protected abstract void writeExtraction() throws AsyncApiException, ExtractExcep @Override protected boolean writeStatus() { - return getConfig().getBoolean(Config.ENABLE_EXTRACT_STATUS_OUTPUT); + return getConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT); } private String getSoql() { @@ -111,12 +126,96 @@ private DataWriter getQueryWriter() { } protected void addResultRow(Row row, String id) throws DataAccessObjectException { + if (controller.getAppConfig().getBoolean(AppConfig.PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS)) { + getRTFDataForRow(row); + } this.batchRows.add(row); this.batchIds.add(id); if (this.batchSize == this.batchRows.size()) { writeBatch(); } } + + + private static final String IMG_TAG_SRC_ATTR_PATTERN = "]*?\\s+)?src=\"([^\"]*)\"(?:\\s+[^>]*?)?>"; + private void getRTFDataForRow(Row row) { + for (String colName : row.keySet()) { + Object colVal = row.get(colName); + boolean isColValModified = false; + if (colVal == null) { + continue; + } + String strValOfCol = colVal.toString(); + String[] outsideIMGTagSrcAttrParts = strValOfCol.split(IMG_TAG_SRC_ATTR_PATTERN); + Pattern htmlTagInRichTextPattern = Pattern.compile(IMG_TAG_SRC_ATTR_PATTERN); + Matcher matcher = htmlTagInRichTextPattern.matcher(strValOfCol); + int idx = 0; + String newValOfCol = ""; + while (matcher.find()) { + String imageTagSrcAttrValue = matcher.group(); + String[] imageTagParts = imageTagSrcAttrValue.split("src\\s*=\\s*\"([^\"]+)\""); + if (imageTagParts.length == 2 && imageTagSrcAttrValue.contains(".file.force.com/servlet/rtaImage?")) { + String srcAttrWithBinaryContent = imageTagSrcAttrValue.substring(imageTagParts[0].length(), imageTagSrcAttrValue.length() - imageTagParts[1].length()); + String[] srcAttrNameValue = srcAttrWithBinaryContent.split("=", 2); + String binaryContent = getBinaryContentForURL(srcAttrNameValue[1].replace("\"",""), colName); + imageTagSrcAttrValue = imageTagParts[0] + " src=\"data:image/png;base64," + binaryContent + "\"" + imageTagParts[1]; + isColValModified = true; + } + if (idx >= outsideIMGTagSrcAttrParts.length) { + newValOfCol += imageTagSrcAttrValue; + } else { + newValOfCol += outsideIMGTagSrcAttrParts[idx] + imageTagSrcAttrValue; + } + idx++; + } + if (outsideIMGTagSrcAttrParts.length > idx) { + newValOfCol += outsideIMGTagSrcAttrParts[idx]; + } + if (isColValModified) { + row.put(colName, newValOfCol); + } + } + } + + private String getBinaryContentForURL(String urlStr, String fieldName) { + try { + urlStr = java.net.URLDecoder.decode(urlStr, StandardCharsets.UTF_8.name()); + URI uri = new URI(urlStr); + String queryStr = uri.getQuery(); + String[] queryParams = queryStr.split("&"); + String sobjectId = ""; + String refId = ""; + for (String param : queryParams) { + String[] nameValPair = param.split("="); + if (nameValPair[0].equals("eid")) { + sobjectId = nameValPair[1]; + } else if (nameValPair[0].equals("refid")) { + refId = nameValPair[1]; + } + } + urlStr = "https://" + + uri.getHost() + + "/services/data/v" + + Controller.getAPIVersion() + + "/sobjects/" + + controller.getAppConfig().getString(AppConfig.PROP_ENTITY) + + "/" + + sobjectId + + "/richTextImageFields/" + + fieldName + + "/" + + refId; + + HttpClientTransport transport = HttpClientTransport.getInstance(); + transport.setConfig(controller.getClient().getConnectorConfig()); + InputStream is = transport.httpGet(urlStr); + byte[] binaryResponse = is.readAllBytes(); + return Base64.getEncoder().encodeToString(binaryResponse); + } catch (Exception e) { + logger.warn("Unable get image data : " + e.getMessage()); + return urlStr; + } + } private void flushResults() throws DataAccessObjectException { if (!this.batchRows.isEmpty()) { @@ -131,7 +230,7 @@ private void writeBatch() throws DataAccessObjectException { writeSuccesses(); } else { writeErrors(Messages.getMessage(getClass(), "statusErrorNotWritten", - getConfig().getString(Config.DAO_NAME))); + getConfig().getString(AppConfig.PROP_DAO_NAME))); } getProgressMonitor().worked(this.batchRows.size()); getProgressMonitor().setSubTask(getRateCalculator().calculateSubTask(getNumberOfRows(), getNumberErrors())); @@ -139,7 +238,7 @@ private void writeBatch() throws DataAccessObjectException { throw ex; } catch (final DataAccessObjectException ex) { writeErrors(Messages.getMessage(getClass(), "statusErrorNotWrittenException", - getConfig().getString(Config.DAO_NAME), ex.getMessage())); + getConfig().getString(AppConfig.PROP_DAO_NAME), ex.getMessage())); } finally { this.batchRows.clear(); this.batchIds.clear(); @@ -149,39 +248,60 @@ private void writeBatch() throws DataAccessObjectException { private void writeSuccesses() throws DataAccessObjectException { final String msg = Messages.getMessage(getClass(), "statusItemQueried"); final Iterator ids = this.batchIds.iterator(); + if (this.batchRows == null || this.batchRows.isEmpty()) { + return; + } + ArrayList headerColumnList = new ArrayList(); + headerColumnList.add(AppConfig.ID_COLUMN_NAME); + Row firstRow = this.batchRows.get(0); + for (String fieldName : firstRow.keySet()) { + headerColumnList.add(fieldName); + } + headerColumnList.add(AppConfig.STATUS_COLUMN_NAME); + TableHeader header = new TableHeader(headerColumnList); for (final Row row : this.batchRows) { - writeSuccess(row, ids.next(), msg); + writeSuccess(row.convertToTableRow(header), ids.next(), msg); } } private void writeErrors(String errorMessage) throws DataAccessObjectException { + if (this.batchRows == null || this.batchRows.isEmpty()) { + return; + } + ArrayList headerColumnList = new ArrayList(); + Row firstRow = this.batchRows.get(0); + for (String fieldName : firstRow.keySet()) { + headerColumnList.add(fieldName); + } + headerColumnList.add(AppConfig.ERROR_COLUMN_NAME); + TableHeader header = new TableHeader(headerColumnList); for (final Row row : this.batchRows) { - writeError(row, errorMessage); + writeError(row.convertToTableRow(header), errorMessage); } } protected int getWriteBatchSize() { int daoBatchSize; try { - daoBatchSize = getConfig().getInt(Config.DAO_WRITE_BATCH_SIZE); - if (daoBatchSize > Config.MAX_DAO_WRITE_BATCH_SIZE) { - daoBatchSize = Config.MAX_DAO_WRITE_BATCH_SIZE; + daoBatchSize = getConfig().getInt(AppConfig.PROP_DAO_WRITE_BATCH_SIZE); + if (daoBatchSize > AppConfig.MAX_DAO_WRITE_BATCH_SIZE) { + daoBatchSize = AppConfig.MAX_DAO_WRITE_BATCH_SIZE; } } catch (final ParameterLoadException e) { // warn about getting batch size parameter, otherwise continue w/ default getLogger().warn( Messages.getMessage(getClass(), "errorGettingBatchSize", - String.valueOf(Config.DEFAULT_DAO_WRITE_BATCH_SIZE), e.getMessage())); - daoBatchSize = Config.DEFAULT_DAO_WRITE_BATCH_SIZE; + String.valueOf(AppConfig.DEFAULT_DAO_WRITE_BATCH_SIZE), e.getMessage())); + daoBatchSize = AppConfig.DEFAULT_DAO_WRITE_BATCH_SIZE; } return daoBatchSize; } protected void startWriteExtraction(int size) { - getRateCalculator().start(); - getRateCalculator().setNumRecords(size); + getRateCalculator().start(size); // start the Progress Monitor getProgressMonitor().beginTask(Messages.getMessage(getClass(), "extracting"), size); //$NON-NLS-1$ + getProgressMonitor().setSubTask(getRateCalculator().calculateSubTask(getNumberOfRows(), getNumberErrors())); } @Override diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/AbstractVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/AbstractVisitor.java index 7dfc20b90..1585d4219 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/AbstractVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/AbstractVisitor.java @@ -27,30 +27,35 @@ package com.salesforce.dataloader.action.visitor; import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataWriter; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.mapping.Mapper; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import com.salesforce.dataloader.util.LoadRateCalculator; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; + +import java.io.InputStream; +import java.util.Map; + +import com.salesforce.dataloader.util.DLLogManager; public abstract class AbstractVisitor implements IVisitor { private final Logger logger; - private final Controller controller; + protected final Controller controller; private final ILoaderProgress monitor; - private final DataWriter successWriter; - private final DataWriter errorWriter; - private int errors; - private int successes; + private DataWriter successWriter; + private DataWriter errorWriter; + private long errors; + private long successes; private final LoadRateCalculator rateCalculator; public AbstractVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, DataWriter errorWriter) { - this.logger = Logger.getLogger(getClass()); + this.logger = DLLogManager.getLogger(getClass()); this.controller = controller; this.monitor = monitor; this.successWriter = successWriter; @@ -59,23 +64,42 @@ public AbstractVisitor(Controller controller, ILoaderProgress monitor, DataWrite } protected abstract boolean writeStatus(); + + protected void setSuccessWriter(DataWriter successWriter) { + this.successWriter = successWriter; + } + protected void setErrorWriter(DataWriter errorWriter) { + this.errorWriter = errorWriter; + } protected void addSuccess() { this.successes++; } + + protected void addErrors() { + this.errors++; + } + + protected void setSuccesses(long num) { + this.successes = num; + } + + protected void setErrors(long num) { + this.errors = num; + } @Override - public int getNumberOfRows() { + public long getNumberOfRows() { return getNumberErrors() + getNumberSuccesses(); } @Override - public int getNumberErrors() { + public long getNumberErrors() { return this.errors; } @Override - public int getNumberSuccesses() { + public long getNumberSuccesses() { return this.successes; } @@ -91,40 +115,55 @@ protected ILoaderProgress getProgressMonitor() { return this.monitor; } - protected Config getConfig() { - return getController().getConfig(); + protected AppConfig getConfig() { + return getController().getAppConfig(); } protected Mapper getMapper() { return getController().getMapper(); } + + protected DataWriter getErrorWriter() { + return this.errorWriter; + } + + protected DataWriter getSuccessWriter() { + return this.successWriter; + } - protected void writeSuccess(Row row, String id, String message) throws DataAccessObjectException { + protected void writeSuccess(TableRow row, String id, String message) throws DataAccessObjectException { if (writeStatus()) { if (id != null && id.length() > 0) { - row.put(Config.ID_COLUMN_NAME, id); + row.put(AppConfig.ID_COLUMN_NAME, id); } if (message != null && message.length() > 0) { - row.put(Config.STATUS_COLUMN_NAME, message); + row.put(AppConfig.STATUS_COLUMN_NAME, message); } this.successWriter.writeRow(row); } addSuccess(); } - protected void writeError(Row row, String errorMessage) throws DataAccessObjectException { + protected void writeError(TableRow row, String errorMessage) throws DataAccessObjectException { if (writeStatus()) { if (row == null) { - row = Row.singleEntryImmutableRow(Config.ERROR_COLUMN_NAME, errorMessage); + row = TableRow.singleEntryImmutableRow(AppConfig.ERROR_COLUMN_NAME, errorMessage); } else { - row.put(Config.ERROR_COLUMN_NAME, errorMessage); + row.put(AppConfig.ERROR_COLUMN_NAME, errorMessage); } this.errorWriter.writeRow(row); } - this.errors++; + addErrors(); } protected LoadRateCalculator getRateCalculator() { return this.rateCalculator; } + + // Subclasses that support attachments (e.g. BulkLoadVisitor) need to override the method + @Override + public Map getAttachments() { + return null; + } + } diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/BulkApiVisitorUtil.java b/src/main/java/com/salesforce/dataloader/action/visitor/BulkApiVisitorUtil.java deleted file mode 100644 index 3c7e64e03..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/BulkApiVisitorUtil.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.action.visitor; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.text.NumberFormat; -import java.util.HashMap; -import java.util.Map; - -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.exception.ParameterLoadException; -import com.salesforce.dataloader.util.LoadRateCalculator; -import com.sforce.async.AsyncApiException; -import com.sforce.async.BatchInfo; -import com.sforce.async.BatchInfoList; -import com.sforce.async.BulkConnection; -import com.sforce.async.CSVReader; -import com.sforce.async.ConcurrencyMode; -import com.sforce.async.ContentType; -import com.sforce.async.JobInfo; -import com.sforce.async.OperationEnum; - -class BulkApiVisitorUtil { - - private static final Logger logger = Logger.getLogger(BulkApiVisitorUtil.class); - - private final BulkConnection client; - - private JobInfo jobInfo; - private int recordsProcessed; - - private final Map attachments = new HashMap(); - private int attachmentNum; - - private final long checkStatusInterval; - private long lastStatusUpdate; - - private final ILoaderProgress monitor; - private final LoadRateCalculator rateCalc; - - private final boolean updateProgress; - - BulkApiVisitorUtil(Controller ctl, ILoaderProgress monitor, LoadRateCalculator rateCalc, boolean updateProgress) { - this.client = ctl.getBulkClient().getClient(); - try { - // getLong will return 0 if no value is provided - long checkStatusInt = ctl.getConfig().getLong(Config.BULK_API_CHECK_STATUS_INTERVAL); - this.checkStatusInterval = checkStatusInt > 0 ? checkStatusInt - : Config.DEFAULT_BULK_API_CHECK_STATUS_INTERVAL; - } catch (ParameterLoadException e) { - throw new RuntimeException("Failed to initialize check status interval", e); - } - this.monitor = monitor; - this.rateCalc = rateCalc; - this.updateProgress = updateProgress; - } - - BulkApiVisitorUtil(Controller ctl, ILoaderProgress monitor, LoadRateCalculator rateCalc) { - this(ctl, monitor, rateCalc, true); - } - - String getJobId() { - return this.jobInfo.getId(); - } - - void createJob(Config cfg) throws AsyncApiException { - JobInfo job = new JobInfo(); - final OperationEnum op = cfg.getOperationInfo().getOperationEnum(); - job.setOperation(op); - if (op == OperationEnum.upsert) { - job.setExternalIdFieldName(cfg.getString(Config.EXTERNAL_ID_FIELD)); - } - job.setObject(cfg.getString(Config.ENTITY)); - job.setContentType(cfg.getBoolean(Config.BULK_API_ZIP_CONTENT) && op != OperationEnum.query ? ContentType.ZIP_CSV - : ContentType.CSV); - job.setConcurrencyMode(cfg.getBoolean(Config.BULK_API_SERIAL_MODE) ? ConcurrencyMode.Serial - : ConcurrencyMode.Parallel); - - if (op == OperationEnum.update || op == OperationEnum.upsert || op == OperationEnum.insert) { - final String assRule = cfg.getString(Config.ASSIGNMENT_RULE); - if (assRule != null && (assRule.length() == 15 || assRule.length() == 18)) { - job.setAssignmentRuleId(assRule); - } - } - job = this.client.createJob(job); - logger.info(Messages.getMessage(getClass(), "logJobCreated", job.getId())); - this.jobInfo = job; - } - - private static final NumberFormat FILE_NUM_FMT; - static { - final NumberFormat fmt = NumberFormat.getIntegerInstance(); - fmt.setGroupingUsed(false); - fmt.setMinimumIntegerDigits(3); - FILE_NUM_FMT = fmt; - } - - String addAttachment(byte[] fileContents) { - final String name = "attachment_" + FILE_NUM_FMT.format(this.attachmentNum++); - this.attachments.put(name, new ByteArrayInputStream(fileContents)); - return "#" + name; - } - - BatchInfo createBatch(InputStream batchContent) throws AsyncApiException { - BatchInfo batch; - if (this.jobInfo.getContentType() == ContentType.ZIP_CSV) { - batch = this.client.createBatchWithInputStreamAttachments(this.jobInfo, batchContent, this.attachments); - } else { - batch = this.client.createBatchFromStream(this.jobInfo, batchContent); - } - logger.info(Messages.getMessage(getClass(), "logBatchLoaded", batch.getId())); - return batch; - } - - long periodicCheckStatus() throws AsyncApiException { - if (this.monitor.isCanceled()) return 0; - final long timeRemaining = this.checkStatusInterval - (System.currentTimeMillis() - this.lastStatusUpdate); - if (timeRemaining <= 0) { - this.jobInfo = this.client.getJobStatus(getJobId()); - updateJobStatus(); - return this.checkStatusInterval; - } - monitor.setNumberBatchesTotal(jobInfo.getNumberBatchesTotal()); - return timeRemaining; - } - - private void awaitJobCompletion() throws AsyncApiException { - long sleepTime = periodicCheckStatus(); - while (this.jobInfo.getNumberBatchesQueued() > 0 || this.jobInfo.getNumberBatchesInProgress() > 0) { - if (this.monitor.isCanceled()) return; - try { - Thread.sleep(sleepTime); - } catch (final InterruptedException e) {} - sleepTime = periodicCheckStatus(); - } - } - - boolean hasJob() { - return this.jobInfo != null; - } - - void closeJob() throws AsyncApiException { - this.jobInfo = this.client.closeJob(getJobId()); - updateJobStatus(); - awaitJobCompletion(); - } - - private void updateJobStatus() { - if (updateProgress) { - this.monitor.worked(this.jobInfo.getNumberRecordsProcessed() - this.recordsProcessed); - this.monitor.setSubTask(this.rateCalc.calculateSubTask(this.jobInfo.getNumberRecordsProcessed(), - this.jobInfo.getNumberRecordsFailed())); - this.recordsProcessed = this.jobInfo.getNumberRecordsProcessed(); - } - this.lastStatusUpdate = System.currentTimeMillis(); - logger.info(Messages.getMessage(getClass(), "logJobStatus", this.jobInfo.getNumberBatchesQueued(), - this.jobInfo.getNumberBatchesInProgress(), this.jobInfo.getNumberBatchesCompleted(), - this.jobInfo.getNumberBatchesFailed())); - } - - BatchInfoList getBatches() throws AsyncApiException { - return this.client.getBatchInfoList(getJobId()); - } - - CSVReader getBatchResults(String batchId) throws AsyncApiException { - return new CSVReader(this.client.getBatchResultStream(getJobId(), batchId)); - } - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/BulkLoadVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/BulkLoadVisitor.java deleted file mode 100644 index 80a365835..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/BulkLoadVisitor.java +++ /dev/null @@ -1,480 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.PrintStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TimeZone; -import java.util.TreeSet; - -import org.apache.commons.beanutils.DynaBean; -import org.apache.commons.beanutils.DynaProperty; -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataReader; -import com.salesforce.dataloader.dao.DataWriter; -import com.salesforce.dataloader.exception.DataAccessObjectException; -import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; -import com.salesforce.dataloader.exception.LoadException; -import com.salesforce.dataloader.exception.OperationException; -import com.salesforce.dataloader.model.NACalendarValue; -import com.salesforce.dataloader.model.NATextValue; -import com.salesforce.dataloader.model.Row; -import com.salesforce.dataloader.util.DAORowUtil; -import com.sforce.async.AsyncApiException; -import com.sforce.async.AsyncExceptionCode; -import com.sforce.async.BatchInfo; -import com.sforce.async.BatchStateEnum; -import com.sforce.async.CSVReader; - -/** - * Visitor for operations using the bulk API client - * - * @author Jesper Joergensen, Colin Jarvis - * @since 17.0 - */ -public class BulkLoadVisitor extends DAOLoadVisitor { - - private static final Logger logger = Logger.getLogger(BulkLoadVisitor.class); - - private static final String SUCCESS_RESULT_COL = "Success"; - private static final String ERROR_RESULT_COL = "Error"; - private static final String ID_RESULT_COL = "Id"; - private static final String CREATED_RESULT_COL = "Created"; - private static final String SKIP_BATCH_ID = "SKIP"; - - private final boolean isDelete; - private static final DateFormat DATE_FMT; - - static { - DATE_FMT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - DATE_FMT.setTimeZone(TimeZone.getTimeZone("GMT")); - } - private final BulkApiVisitorUtil jobUtil; - - // This keeps track of all the batches we send in order so that we know whats what when processsing results - private final List allBatchesInOrder = new ArrayList(); - - /** DataLoader uses this to help match batch results from SFDC to the rows in our input */ - private class BatchData { - final String batchId; - final int numRows; - - BatchData(String batchId, int numRows) { - this.batchId = batchId; - this.numRows = numRows; - } - } - - /** When we get batch CSV results back from sfdc they are converted into instances of RowResult */ - private static class RowResult { - RowResult(boolean success, boolean created, String id, String error) { - this.success = success; - this.created = created; - this.id = id; - this.error = error; - } - - final boolean success; - final boolean created; - final String id; - final String error; - } - - public BulkLoadVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, - DataWriter errorWriter) { - super(controller, monitor, successWriter, errorWriter); - this.isDelete = getController().getConfig().getOperationInfo().isDelete(); - this.jobUtil = new BulkApiVisitorUtil(getController(), getProgressMonitor(), getRateCalculator()); - } - - @Override - protected void loadBatch() throws DataAccessObjectException, OperationException { - try { - if (!this.jobUtil.hasJob()) this.jobUtil.createJob(getConfig()); - createBatches(); - clearArrays(); - } catch (final AsyncApiException e) { - handleException(e); - } catch (final IOException e) { - handleException(e); - } - } - - /** - * Throws a load exception - */ - @Override - protected void handleException(Throwable t) throws LoadException { - handleException(getOverrideMessage(t), t); - } - - private String getOverrideMessage(Throwable t) { - if (t instanceof AsyncApiException) { - - final AsyncApiException aae = (AsyncApiException)t; - final String hardDeleteNoPermsMessage = "hardDelete operation requires special user profile permission, please contact your system administrator"; - - if (aae.getExceptionCode() == AsyncExceptionCode.FeatureNotEnabled - && aae.getExceptionMessage().contains(hardDeleteNoPermsMessage)) - return Messages.getMessage(getClass(), "hardDeleteNoPerm"); - } - return null; - } - - private void createBatches() throws OperationException, IOException, AsyncApiException { - final ByteArrayOutputStream os = new ByteArrayOutputStream(); - final PrintStream out = new PrintStream(os, true, Config.BULK_API_ENCODING); - doOneBatch(out, os, this.dynaArray); - } - - private void doOneBatch(PrintStream out, ByteArrayOutputStream os, List rows) throws OperationException, - AsyncApiException { - int recordsInBatch = 0; - final List userColumns = getController().getDao().getColumnNames(); - List headerColumns = null; - for (int i = 0; i < rows.size(); i++) { - final DynaBean row = rows.get(i); - - if (recordsInBatch == 0) { - headerColumns = addHeader(out, os, row, userColumns); - } - writeRow(row, out, os, recordsInBatch, headerColumns); - recordsInBatch++; - - if (os.size() > Config.MAX_BULK_API_BATCH_BYTES) { - createBatch(os, recordsInBatch); // resets outputstream - recordsInBatch = 0; - } - } - if (recordsInBatch > 0) createBatch(os, recordsInBatch); - this.jobUtil.periodicCheckStatus(); - } - - private void writeRow(DynaBean row, PrintStream out, ByteArrayOutputStream os, int recordsInBatch, - List header) throws LoadException { - boolean notFirst = false; - for (final String column : header) { - if (notFirst) { - out.print(','); - } else { - notFirst = true; - } - writeSingleColumn(out, column, row.get(column)); - } - out.println(); - } - - private void writeSingleColumn(PrintStream out, String fieldName, Object fieldValue) throws LoadException { - if (fieldValue != null) { - Object col = fieldValue; - if (fieldValue instanceof NACalendarValue) { - col = fieldValue.toString(); - } else if (fieldValue instanceof Calendar) { - col = DATE_FMT.format(((Calendar) fieldValue).getTime()); - } else if (fieldValue instanceof byte[]) { - if (!getController().attachmentsEnabled()) - throw new LoadException(Messages.getMessage("FinishPage", "cannotMapBase64ForBulkApi", fieldName)); - col = this.jobUtil.addAttachment((byte[])fieldValue); - } - writeColumnToCsv(out, col); - } else { - // all null values should be ignored when using bulk API - getLogger().warn(Messages.getMessage(getClass(), "noFieldVal", fieldName)); - } - } - - private void writeColumnToCsv(PrintStream out, Object val) { - out.print('"'); - out.print(val.toString().replace("\"", "\"\"")); - out.print('"'); - } - - private List addHeader(PrintStream out, ByteArrayOutputStream os, DynaBean row, List columns) - throws LoadException { - boolean first = true; - final List cols = new ArrayList(); - final Set addedCols = new TreeSet(String.CASE_INSENSITIVE_ORDER); - for (final String userColumn : columns) { - final String sfdcColumn = getMapper().getMapping(userColumn); - // if the column is not mapped, don't send it - if (sfdcColumn == null || sfdcColumn.length() == 0) { - // TODO: we should make it more obvious to users when we omit a column - getLogger().warn("Cannot find mapping for column: " + userColumn + ". Omitting column"); - continue; - } - // TODO we don't really need to be this strict about a delete CSV file.. as long as the IDS are there - if (this.isDelete && (!first || !"id".equalsIgnoreCase(sfdcColumn))) - throw new LoadException(Messages.getMessage(getClass(), "deleteCsvError")); - addFieldToHeader(out, sfdcColumn, cols, addedCols, first); - if (first) first = false; - } - for (DynaProperty dynaProperty : row.getDynaClass().getDynaProperties()) { - final String name = dynaProperty.getName(); - if (row.get(name) != null && !addedCols.contains(name)) { - addFieldToHeader(out, name, cols, addedCols, first); - } - } - out.println(); - return Collections.unmodifiableList(cols); - } - - private static void addFieldToHeader(PrintStream out, String sfdcColumn, List cols, Set addedCols, - boolean first) { - if (!first) { - out.print(','); - } - out.print(sfdcColumn.replace(':', '.')); - cols.add(sfdcColumn); - addedCols.add(sfdcColumn); - } - - private void createBatch(ByteArrayOutputStream os, int numRecords) throws AsyncApiException { - if (numRecords <= 0) return; - final byte[] request = os.toByteArray(); - os.reset(); - BatchInfo bi = this.jobUtil.createBatch(new ByteArrayInputStream(request, 0, request.length)); - this.allBatchesInOrder.add(new BatchData(bi.getId(), numRecords)); - } - - @Override - public void flushRemaining() throws OperationException, DataAccessObjectException { - super.flushRemaining(); - if (this.jobUtil.hasJob()) { - try { - this.jobUtil.closeJob(); - } catch (final AsyncApiException e) { - logger.warn("Failed to close job", e); - } - try { - getResults(); - } catch (AsyncApiException e) { - throw new LoadException("Failed to get batch results", e); - } - } - } - - private void getResults() throws AsyncApiException, OperationException, DataAccessObjectException { - - getProgressMonitor().setSubTask(Messages.getMessage(getClass(), "retrievingResults")); - - final DataReader dataReader = resetDAO(); - - // create a map of batch infos by batch id. Each batchinfo has the final processing state of the batch - final Map batchInfoMap = createBatchInfoMap(); - - // go through all the batches we sent to sfdc in the same order and process the batch results for - // each one by looking them up in batchInfoMap - for (final BatchData clientBatchInfo : this.allBatchesInOrder) { - if (clientBatchInfo.batchId == SKIP_BATCH_ID) { - skipDataRows(dataReader, clientBatchInfo.numRows); - } else { - processResults(dataReader, batchInfoMap.get(clientBatchInfo.batchId), clientBatchInfo); - } - } - } - - private void processResults(final DataReader dataReader, final BatchInfo batch, BatchData clientBatchInfo) - throws LoadException, DataAccessObjectException, AsyncApiException { - // For Bulk API, we don't save any success or error until the end, - // so we have to go through the original CSV from the beginning while - // we go through the results from the server. - // TODO we should save the ACTUAL rows/batches sent to the server - - // do some basic checks to make sure we are matching up the batches correctly - sanityCheckBatch(clientBatchInfo, batch); - - // If there was an error processing the batch and the state isn't 'Completed' get stateMessage from batch - final String stateMessage = (batch.getState() == BatchStateEnum.Completed) ? null : batch.getStateMessage(); - final String errorMessage = stateMessage == null ? null : Messages.getMessage(getClass(), "batchError", - stateMessage); - - final List rows = dataReader.readRowList(clientBatchInfo.numRows); - if (batch.getState() == BatchStateEnum.Completed || batch.getNumberRecordsProcessed() > 0) { - try { - processBatchResults(batch, errorMessage, batch.getState(), rows); - } catch (IOException e) { - throw new LoadException("IOException while reading batch results", e); - } - } else { - for (final Row row : rows) { - writeError(row, errorMessage); - } - } - } - - private void skipDataRows(DataReader dataReader, int numRows) throws DataAccessObjectException { - List skippedRows = dataReader.readRowList(numRows); - assert skippedRows.size() == numRows; - } - - private void processBatchResults(final BatchInfo batch, final String errorMessage, final BatchStateEnum state, - final List rows) throws DataAccessObjectException, IOException, AsyncApiException { - - // get the batch csv result stream from sfdc - final CSVReader resultRdr = this.jobUtil.getBatchResults(batch.getId()); - - // read in the result csv header and note the column indices - Map hdrIndices = mapHeaderIndices(resultRdr.nextRecord()); - final int successIdx = hdrIndices.get(SUCCESS_RESULT_COL); - final int createdIdx = isDelete ? -1 : hdrIndices.get(CREATED_RESULT_COL); - final int idIdx = hdrIndices.get(ID_RESULT_COL); - final int errIdx = hdrIndices.get(ERROR_RESULT_COL); - hdrIndices = null; - - for (final Row row : rows) { - final List res = resultRdr.nextRecord(); - - // no result for this column. In this case it failed, and we should use the batch state message - if (state == BatchStateEnum.Failed || errorMessage != null) { - getLogger().warn( - Messages.getMessage(getClass(), "logBatchInfoWithMessage", batch.getId(), state, errorMessage)); - writeError(row, errorMessage); - } else if (res == null || res.isEmpty()) { - String msg = Messages.getMessage(getClass(), "noResultForRow", batch.getId(), state); - writeError(row, msg); - getLogger().warn(msg); - } else { - // convert the row into a RowResults so its easy to inspect - final RowResult rowResult = new RowResult(Boolean.valueOf(res.get(successIdx)), isDelete ? false - : Boolean.valueOf(res.get(createdIdx)), res.get(idIdx), res.get(errIdx)); - writeRowResult(row, rowResult); - } - } - } - - // returns a map of batchinfos indexed by batch id - private Map createBatchInfoMap() throws AsyncApiException { - Map batchInfoMap = new HashMap(); - for (BatchInfo bi : this.jobUtil.getBatches().getBatchInfo()) { - batchInfoMap.put(bi.getId(), bi); - } - return batchInfoMap; - } - - private DataReader resetDAO() throws DataAccessObjectInitializationException, LoadException { - final DataReader dataReader = (DataReader)getController().getDao(); - dataReader.close(); - // TODO: doing this causes sql to be executed twice, for sql we should cache results in a local file - dataReader.open(); - // when re-opening the dao we need to start at the same row in the input - DAORowUtil.get().skipRowToStartOffset(getConfig(), dataReader, getProgressMonitor(), true); - return dataReader; - } - - private void writeRowResult(Row row, RowResult resultRow) throws DataAccessObjectException { - if (resultRow.success) { - String successMessage; - switch (getConfig().getOperationInfo()) { - case hard_delete: - successMessage = "statusItemHardDeleted";//$NON-NLS-1$ - break; - case delete: - successMessage = "statusItemDeleted";//$NON-NLS-1$ - break; - default: - successMessage = resultRow.created ? "statusItemCreated"//$NON-NLS-1$ - : "statusItemUpdated"; //$NON-NLS-1$ - } - writeSuccess(row, resultRow.id, Messages.getMessage(getClass(), successMessage)); - } else { - writeError(row, parseAsyncApiError(resultRow.error)); - } - } - - // creates a map from the header strings in the result csv to the Integer index in the list - private Map mapHeaderIndices(final List header) { - final Map indices = new HashMap(); - for (int i = 0; i < header.size(); i++) - indices.put(header.get(i), i); - return indices; - } - - private void sanityCheckBatch(BatchData clientBatchInfo, BatchInfo batch) throws LoadException { - final String batchId = clientBatchInfo.batchId; - - assert (batchId != null && batchId.equals(batch.getId())); - assert (jobUtil.getJobId().equals(batch.getJobId())); - assert clientBatchInfo.numRows > 0; - - final int recordsProcessed = batch.getNumberRecordsProcessed(); - final BatchStateEnum state = batch.getState(); - - if (state != BatchStateEnum.Completed && state != BatchStateEnum.Failed) - sanityCheckError(batchId, "Expected batch state to be Completed or Failed, but was " + state); - } - - private void sanityCheckError(String id, String errMsg) throws LoadException { - throw new LoadException(id + ": " + errMsg); - } - - private String parseAsyncApiError(final String errString) { - final String sep = ":"; - final String suffix = "--"; - final int lastSep = errString.lastIndexOf(sep); - if (lastSep > 0 && errString.endsWith(suffix)) { - final String fields = errString.substring(lastSep + 1, errString.length() - suffix.length()); - final String start = errString.substring(0, lastSep); - if (fields != null && fields.length() > 0) - return new StringBuilder(start).append("\n").append("Error fields: ").append(fields).toString(); - return start; - } - return errString; - } - - @Override - protected void convertBulkAPINulls(Row row) { - for (final Map.Entry entry : row.entrySet()) { - if (NATextValue.isNA(entry.getValue())) { - entry.setValue(NATextValue.getInstance()); - } - } - } - - @Override - protected void conversionFailed(Row row, String errMsg) throws DataAccessObjectException, - OperationException { - super.conversionFailed(row, errMsg); - getLogger().warn("Skipping results for row " + row + " which failed before upload to Saleforce.com"); - allBatchesInOrder.add(new BatchData(SKIP_BATCH_ID, 1)); - } -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/BulkQueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/BulkQueryVisitor.java deleted file mode 100644 index dbd46c21f..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/BulkQueryVisitor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.UnsupportedEncodingException; -import java.util.List; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.salesforce.dataloader.exception.DataAccessObjectException; -import com.salesforce.dataloader.exception.ExtractException; -import com.salesforce.dataloader.exception.OperationException; -import com.salesforce.dataloader.model.Row; -import com.sforce.async.AsyncApiException; -import com.sforce.async.BatchInfo; -import com.sforce.async.BatchStateEnum; -import com.sforce.async.CSVReader; -import com.sforce.async.QueryResultList; - -/** - * Query visitor for bulk api extract operations. - * - * @author Colin Jarvis - * @since 21.0 - */ -public class BulkQueryVisitor extends AbstractQueryVisitor { - - private BatchInfo batch; - - public BulkQueryVisitor(Controller controller, ILoaderProgress monitor, DataWriter queryWriter, - DataWriter successWriter, DataWriter errorWriter) { - super(controller, monitor, queryWriter, successWriter, errorWriter); - } - - @Override - protected int executeQuery(String soql) throws AsyncApiException, OperationException { - final BulkApiVisitorUtil jobUtil = new BulkApiVisitorUtil(getController(), getProgressMonitor(), - getRateCalculator(), false); - jobUtil.createJob(getConfig()); - try { - jobUtil.createBatch(new ByteArrayInputStream(soql.getBytes(Config.BULK_API_ENCODING))); - } catch (final UnsupportedEncodingException e) { - throw new ExtractException(e); - } - jobUtil.closeJob(); - final BatchInfo b = jobUtil.getBatches().getBatchInfo()[0]; - if (b.getState() == BatchStateEnum.Failed) throw new ExtractException("Batch failed: " + b.getStateMessage()); - this.batch = b; - return b.getNumberRecordsProcessed(); - } - - @Override - protected void writeExtraction() throws AsyncApiException, ExtractException, DataAccessObjectException { - if (this.batch.getState() == BatchStateEnum.Failed) - throw new ExtractException("Batch failed: " + this.batch.getStateMessage()); - final QueryResultList results = getController().getBulkClient().getClient() - .getQueryResultList(this.batch.getJobId(), this.batch.getId()); - - for (final String resultId : results.getResult()) { - if (getProgressMonitor().isCanceled()) return; - try { - final InputStream resultStream = getController().getBulkClient().getClient() - .getQueryResultStream(this.batch.getJobId(), this.batch.getId(), resultId); - try { - final CSVReader rdr = new CSVReader(resultStream, Config.BULK_API_ENCODING); - rdr.setMaxCharsInFile(Integer.MAX_VALUE); - rdr.setMaxRowsInFile(Integer.MAX_VALUE); - List headers; - headers = rdr.nextRecord(); - List csvRow; - while ((csvRow = rdr.nextRecord()) != null) { - final StringBuilder id = new StringBuilder(); - final Row daoRow = getDaoRow(headers, csvRow, id); - addResultRow(daoRow, id.toString()); - } - } finally { - resultStream.close(); - } - } catch (final IOException e) { - throw new ExtractException(e); - } - } - } - - private Row getDaoRow(List headers, List csvRow, StringBuilder id) { - return getMapper().mapCsvRowSfdcToLocal(headers, csvRow, id); - } - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/DAOLoadVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/DAOLoadVisitor.java index 0bc2352e6..8d0132911 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/DAOLoadVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/DAOLoadVisitor.java @@ -26,12 +26,26 @@ package com.salesforce.dataloader.action.visitor; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.*; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.DAORowUtil; + import org.apache.commons.beanutils.*; +import org.apache.commons.text.StringEscapeUtils; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.client.SessionInfo; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.LastRunProperties; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataReader; @@ -40,6 +54,10 @@ import com.salesforce.dataloader.exception.*; import com.salesforce.dataloader.mapping.LoadMapper; import com.sforce.async.AsyncApiException; +import com.sforce.soap.partner.DescribeSObjectResult; +import com.sforce.soap.partner.Error; +import com.sforce.soap.partner.Field; +import com.sforce.soap.partner.FieldType; import com.sforce.soap.partner.fault.ApiFault; import com.sforce.ws.ConnectionException; @@ -55,56 +73,188 @@ public abstract class DAOLoadVisitor extends AbstractVisitor implements DAORowVi // this stores the dynabeans, which convert types correctly protected final List dynaArray; - protected final List dataArray; - - protected final BasicDynaClass dynaClass; - protected final DynaProperty[] dynaProps; + protected int dynaArraySize = 0; + private HashMap rowConversionFailureMap; - private final int batchSize; + protected BasicDynaClass dynaClass = null; + protected DynaProperty[] dynaProps = null; + private final int MAX_ROWS_IN_BATCH; + protected List daoRowList = new ArrayList(); + protected ArrayList batchRowToDAORowList = new ArrayList(); + private int processedDAORowCounter = 0; + private static final Logger logger = DLLogManager.getLogger(DAOLoadVisitor.class); + // following regex pattern is based on info from: + // - https://www.regular-expressions.info/lookaround.html + // - https://www.geeksforgeeks.org/how-to-validate-html-tag-using-regular-expression/# + private String richTextRegex = AppConfig.DEFAULT_RICHTEXT_REGEX; + private Field[] cachedFieldAttributesForOperation = null; + protected DAOLoadVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, DataWriter errorWriter) { super(controller, monitor, successWriter, errorWriter); this.columnNames = ((DataReader)controller.getDao()).getColumnNames(); - dynaArray = new LinkedList(); - dataArray = new LinkedList(); - + List dynaList = null; + try { + dynaList = new ArrayList(((DataReader)controller.getDao()).getTotalRows()); + } catch (DataAccessObjectException e) { + dynaList = new ArrayList(); + } + dynaArray = dynaList; SforceDynaBean.registerConverters(getConfig()); - dynaProps = SforceDynaBean.createDynaProps(controller.getFieldTypes(), controller); - dynaClass = SforceDynaBean.getDynaBeanInstance(dynaProps); - - this.batchSize = getConfig().getLoadBatchSize(); + this.MAX_ROWS_IN_BATCH = getConfig().getMaxRowsInImportBatch(); + rowConversionFailureMap = new HashMap(); + String newRichTextRegex = getConfig().getString(AppConfig.PROP_RICH_TEXT_FIELD_REGEX); + if (newRichTextRegex != null + && !newRichTextRegex.isBlank() + && !newRichTextRegex.equals(richTextRegex)) { + this.richTextRegex = newRichTextRegex; + } + this.initLoadRateCalculator(); } + + public void setRowConversionStatus(int dataSourceRow, boolean conversionSuccess) { + if (!conversionSuccess) { + this.rowConversionFailureMap.put(dataSourceRow, true); + } + } + + private int rowConversionCheckCounter = 0; + private boolean gotSkippedRowsCount = false; + private int skippedRowsCount = 0; + protected boolean isRowConversionSuccessful() { + if (!gotSkippedRowsCount) { + try { + skippedRowsCount = controller.getAppConfig().getInt(AppConfig.PROP_LOAD_ROW_TO_START_AT); + gotSkippedRowsCount = true; + } catch (ParameterLoadException e) { + // @ignored + } + } + int rowToCheck = skippedRowsCount + rowConversionCheckCounter++; + Boolean conversionFailure = this.rowConversionFailureMap.get(rowToCheck); + if (conversionFailure != null && conversionFailure.booleanValue()) { + return false; + } + return true; // no entry in the list of failed conversions means successful conversion + } + + private int bytesInBatch = 0; @Override - public final void visit(Row row) throws OperationException, DataAccessObjectException, - ConnectionException { - initLoadRateCalculator(); + public boolean visit(TableRow row) throws OperationException, DataAccessObjectException, + ConnectionException, BatchSizeLimitException { + AppConfig appConfig = controller.getAppConfig(); // the result are sforce fields mapped to data - Row sforceDataRow = getMapper().mapData(row); + TableRow sforceDataRow = getMapper().mapData(row, processedDAORowCounter == 0); + if (this.getConfig().getBoolean(AppConfig.PROP_TRUNCATE_FIELDS) + && this.getConfig().isRESTAPIEnabled() + && "update".equalsIgnoreCase(this.getConfig().getString(AppConfig.PROP_OPERATION))) { + PartnerClient partnerClient = this.getController().getPartnerClient(); + if (cachedFieldAttributesForOperation == null) { + cachedFieldAttributesForOperation = partnerClient.getSObjectFieldAttributesForRow( + this.getConfig().getString(AppConfig.PROP_ENTITY), sforceDataRow); + } + for (String fieldName : sforceDataRow.getHeader().getColumns()) { + for (Field fieldDescribe : cachedFieldAttributesForOperation) { + // Field truncation is applicable to certain field types only. + // See https://developer.salesforce.com/docs/atlas.en-us.api_tooling.meta/api_tooling/sforce_api_header_allowfieldtruncation.htm + // for the list of field types that field truncation is applicable to. + FieldType type = fieldDescribe.getType(); + if (fieldDescribe.getName().equalsIgnoreCase(fieldName) + && (type == FieldType.email + || type == FieldType.string + || type == FieldType.picklist + || type == FieldType.phone + || type == FieldType.textarea + || type == FieldType.multipicklist) + ) { + int fieldLength = fieldDescribe.getLength(); + if (row.get(fieldName).toString().length() > fieldLength) { + if (type == FieldType.email) { + String[] emailParts = row.get(fieldName).toString().split("@"); + if (emailParts.length == 2) { + String firstPart = emailParts[0].substring(0, + fieldLength - emailParts[1].length() - 1); + row.put(fieldName, firstPart + "@" + emailParts[1]); + continue; + } + } + row.put(fieldName, row.get(fieldName).toString().substring(0, fieldLength)); + } + } + } + } + } + convertBulkAPINulls(sforceDataRow); + + // Make sure to initialize dynaClass only after mapping a row. + // This is to make sure that all polymorphic field mappings specified + // in the mapping file are mapped to parent object. + if (dynaProps == null) { + dynaProps = SforceDynaBean.createDynaProps(controller.getFieldTypes(), controller); + } + if (dynaClass == null) { + dynaClass = SforceDynaBean.getDynaBeanInstance(dynaProps); + } try { - convertBulkAPINulls(sforceDataRow); - dynaArray.add(SforceDynaBean.convertToDynaBean(dynaClass, sforceDataRow)); - } catch (ConversionException conve) { + DynaBean dynaBean = SforceDynaBean.convertToDynaBean(dynaClass, sforceDataRow); + Map fieldMap = BeanUtils.describe(dynaBean); + for (String fName : fieldMap.keySet()) { + if (fieldMap.get(fName) != null) { + // see if any entity foreign key references are embedded here + Object value = this.getFieldValue(fName, dynaBean.get(fName)); + dynaBean.set(fName, value); + } + } + + int bytesInBean = getBytesInBean(dynaBean); + if (this.bytesInBatch + bytesInBean > getMaxBytesInBatch()) { + loadBatch(); + this.bytesInBatch = 0; + this.processedDAORowCounter--; // roll back the counter by 1 + throw new BatchSizeLimitException("batch max bytes size reached"); + } + if (appConfig.getBoolean(AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || (!appConfig.isBulkAPIEnabled() && !appConfig.isBulkV2APIEnabled())) { + // either bulk mode or cache bulk data uploaded from DAO + this.daoRowList.add(row); + } + dynaArray.add(dynaBean); + this.bytesInBatch += bytesInBean; + this.batchRowToDAORowList.add(this.processedDAORowCounter); + } catch (ConversionException | IllegalAccessException conve) { String errMsg = Messages.getMessage("Visitor", "conversionErrorMsg", conve.getMessage()); getLogger().error(errMsg, conve); conversionFailed(row, errMsg); - // this row cannot be added since conversion has failed - return; + if (!appConfig.isBulkAPIEnabled() && !appConfig.isBulkV2APIEnabled()) { + // SOAP or REST API use daoRowList to process results of an upload request + this.daoRowList.add(row); + } + return false; + } catch (InvocationTargetException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (NoSuchMethodException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } finally { + this.processedDAORowCounter++; } - // add the data for writing to the result files - // must do this after conversion. - dataArray.add(row); // load the batch - if (dynaArray.size() >= this.batchSize) { + if (dynaArray.size() >= this.MAX_ROWS_IN_BATCH) { loadBatch(); } + return true; } + + protected abstract int getBytesInBean(DynaBean dynaBean); + protected abstract int getMaxBytesInBatch(); /** * @param row @@ -112,25 +262,27 @@ public final void visit(Row row) throws OperationException, DataAccessObjectExce * @throws DataAccessObjectException * @throws OperationException */ - protected void conversionFailed(Row row, String errMsg) throws DataAccessObjectException, + protected void conversionFailed(TableRow row, String errMsg) throws DataAccessObjectException, OperationException { writeError(row, errMsg); } - protected void convertBulkAPINulls(Row row) {} + protected void convertBulkAPINulls(TableRow row) {} - public void flushRemaining() throws OperationException, DataAccessObjectException { + public void flushRemaining() throws OperationException, DataAccessObjectException, BatchSizeLimitException { // check if there are any entities left if (dynaArray.size() > 0) { loadBatch(); } + // clear the caches + cachedFieldAttributesForOperation = null; } - protected abstract void loadBatch() throws DataAccessObjectException, OperationException; + protected abstract void loadBatch() throws DataAccessObjectException, OperationException, BatchSizeLimitException; public void clearArrays() { // clear the arrays - dataArray.clear(); + daoRowList.clear(); dynaArray.clear(); } @@ -144,7 +296,7 @@ protected void handleException(String msgOverride, Throwable t) throws LoadExcep msg = ((ApiFault)t).getExceptionMessage(); } } - throw new LoadException(msg, t); + throw new LoadExceptionOnServer(msg, t); } protected void handleException(Throwable t) throws LoadException { @@ -156,14 +308,176 @@ protected boolean writeStatus() { return true; } - private void initLoadRateCalculator() throws DataAccessObjectException { - getRateCalculator().setNumRecords(((DataReader)getController().getDao()).getTotalRows()); - getRateCalculator().start(); + private void initLoadRateCalculator() { + try { + DataReader dao = (DataReader)getController().getDao(); + getRateCalculator().start(dao.getTotalRows()); + getProgressMonitor().setSubTask(getRateCalculator().calculateSubTask(getNumberOfRows(), getNumberErrors())); + } catch (Exception e) { + logger.error("Unable to get total rows to upload from CSV or database"); + getRateCalculator().start(0); + } } @Override protected LoadMapper getMapper() { return (LoadMapper)super.getMapper(); } + + private static final int NONBREAKING_SPACE_ASCII_VAL = 0xA0; + private static Controller currentController = null; + private ArrayList htmlFormattedSforceFieldList = null; + private ArrayList phoneSforceFieldList = null; + + private synchronized void getHtmlFormattedAndPhoneSforceFieldList() { + if (htmlFormattedSforceFieldList != null && phoneSforceFieldList != null) { + return; // already created + } + if (getController() == currentController && htmlFormattedSforceFieldList != null) { + return; + } + if (getController() == null) { + return; + } + if (!getController().isLoggedIn()) { + // clear cached values if not logged in + currentController = null; + return; + } + currentController = getController(); + htmlFormattedSforceFieldList = new ArrayList(); + phoneSforceFieldList = new ArrayList(); + DescribeSObjectResult result = getController().getFieldTypes(); + Field[] fields = result.getFields(); + for (Field field : fields) { + if (field.getHtmlFormatted()) { + htmlFormattedSforceFieldList.add(field.getName()); + } + if (field.getType() == FieldType.phone) { + phoneSforceFieldList.add(field.getName()); + } + } + } + + public Object getFieldValue(String fieldName, Object fieldValue) { + fieldValue = getHtmlFormattedFieldValue(fieldName, fieldValue); + fieldValue = getPhoneFieldValue(fieldName, fieldValue); + return fieldValue; + } + + private Object getHtmlFormattedFieldValue(String fieldName, Object fieldValue) { + getHtmlFormattedAndPhoneSforceFieldList(); + if (htmlFormattedSforceFieldList == null + || !htmlFormattedSforceFieldList.contains(fieldName) + || !getController().getAppConfig().getBoolean(AppConfig.PROP_LOAD_PRESERVE_WHITESPACE_IN_RICH_TEXT)) { + return fieldValue; + } + return convertToHTMLFormatting((String)fieldValue, this.richTextRegex); + } + public static String convertToHTMLFormatting(String fvalue, String regex) { + fvalue = fvalue.replaceAll("\r\n", "
"); + fvalue = fvalue.replaceAll("\n", "
"); + fvalue = fvalue.replaceAll("\r", "
"); + String[] outsideHTMLTags = fvalue.split(regex); + Pattern htmlTagInRichTextPattern = Pattern.compile(regex); + Matcher matcher = htmlTagInRichTextPattern.matcher(fvalue); + String htmlEscapedValue = ""; + int idx = 0; + while (matcher.find()) { + if (idx >= outsideHTMLTags.length) { + htmlEscapedValue += matcher.group(); + } else { + htmlEscapedValue += escapeHTMLChars(outsideHTMLTags[idx]) + matcher.group(); + } + idx++; + } + if (outsideHTMLTags.length > idx) { + htmlEscapedValue += escapeHTMLChars(outsideHTMLTags[idx]); + } + return htmlEscapedValue; + } + + private static String escapeHTMLChars(String input) { + if (input == null) { + return null; + } + String unescapedInput = StringEscapeUtils.unescapeHtml4(input); + StringBuffer htmlFormattedStr = new StringBuffer(""); + for (int i = 0, len = unescapedInput.length(); i < len; i++) { + char c = unescapedInput.charAt(i); + int cval = c; + char nextChar = 0; + if (i+1 < unescapedInput.length()) { + nextChar = unescapedInput.charAt(i+1); + } + char prevChar = 0; + if (i > 0) { + prevChar = unescapedInput.charAt(i-1); + } + + boolean isCharWhitespace = Character.isWhitespace(c) || cval == NONBREAKING_SPACE_ASCII_VAL; + boolean isNextCharWhitespace = Character.isWhitespace(nextChar) || nextChar == NONBREAKING_SPACE_ASCII_VAL; + boolean isPrevCharWhitespace = Character.isWhitespace(prevChar) || prevChar == NONBREAKING_SPACE_ASCII_VAL; + //only occurrences of multiple w + if (isCharWhitespace) { + if (isNextCharWhitespace || isPrevCharWhitespace) { + htmlFormattedStr.append(" "); + } else { + htmlFormattedStr.append(c); + } + } else { + htmlFormattedStr.append(StringEscapeUtils.escapeHtml4(Character.toString(c))); + } + } + return htmlFormattedStr.toString(); + } + + private Object getPhoneFieldValue(String fieldName, Object fieldValue) { + getHtmlFormattedAndPhoneSforceFieldList(); + if (this.phoneSforceFieldList == null + || !this.phoneSforceFieldList.contains(fieldName) + || !this.getConfig().getBoolean(AppConfig.PROP_FORMAT_PHONE_FIELDS)) { + return fieldValue; + } + String localeStr = Locale.getDefault().toString(); + SessionInfo sessionInfo = this.controller.getPartnerClient().getSession(); + if (sessionInfo != null) { + localeStr = sessionInfo.getUserInfoResult().getUserLocale(); + } + return DAORowUtil.getPhoneFieldValue((String)fieldValue, localeStr); + } + + + protected void processResult(TableRow dataRow, boolean isSuccess, String id, Error[] errors) + throws DataAccessObjectException { + // process success vs. error + // extract error message from error result + if (isSuccess) { + writeSuccess(dataRow, id, null); + } else { + writeError(dataRow, + errors == null ? Messages.getString("Visitor.noErrorReceivedMsg") : errors[0].getMessage()); + } + } + + protected void setLastRunProperties(Object[] results) throws LoadException { + // set the last processed row number in the config (*_lastRun.properties) file + int currentProcessed; + try { + currentProcessed = getConfig().getInt(LastRunProperties.LAST_LOAD_BATCH_ROW); + } catch (ParameterLoadException e) { + // if there's a problem getting last batch row, start at the beginning + currentProcessed = 0; + } + currentProcessed += results.length; + getConfig().setValue(LastRunProperties.LAST_LOAD_BATCH_ROW, currentProcessed); + try { + getConfig().saveLastRun(); + } catch (IOException e) { + String errMsg = Messages.getString("LoadAction.errorLastRun"); + getLogger().error(errMsg, e); + handleException(errMsg, e); + } + } } diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/DAORowVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/DAORowVisitor.java index e2e6ad8fa..13a09595c 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/DAORowVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/DAORowVisitor.java @@ -26,9 +26,10 @@ package com.salesforce.dataloader.action.visitor; +import com.salesforce.dataloader.exception.BatchSizeLimitException; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.OperationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import com.sforce.ws.ConnectionException; /** @@ -38,6 +39,6 @@ */ public interface DAORowVisitor { - public void visit(Row row) throws OperationException, DataAccessObjectException, ConnectionException; + public boolean visit(TableRow row) throws OperationException, DataAccessObjectException, ConnectionException, BatchSizeLimitException; } diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/DAOSizeVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/DAOSizeVisitor.java index f02e6bf92..624526f0d 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/DAOSizeVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/DAOSizeVisitor.java @@ -26,9 +26,8 @@ package com.salesforce.dataloader.action.visitor; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; -import java.util.Map; /** * Visitor to calculate the size of a DataAccessObject @@ -41,8 +40,9 @@ public class DAOSizeVisitor implements DAORowVisitor { private int numberOfRows = 0; @Override - public void visit(Row row) { + public boolean visit(TableRow row) { numberOfRows++; + return true; } public int getNumberOfRows() { diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/DeleteVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/DeleteVisitor.java deleted file mode 100644 index e609a93a1..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/DeleteVisitor.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.util.List; - -import org.apache.commons.beanutils.DynaBean; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.client.PartnerClient; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.sforce.ws.ConnectionException; - -/** - * @author Lexi Viripaeff - * @since 6.0 - */ -public class DeleteVisitor extends PartnerLoadVisitor { - - public DeleteVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, - DataWriter errorWriter) { - super(controller, monitor, successWriter, errorWriter); - } - - @Override - protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { - return client.loadDeletes(dynabeans); - } -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/IVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/IVisitor.java index 4f57cb1f9..0a795fee4 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/IVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/IVisitor.java @@ -26,6 +26,9 @@ package com.salesforce.dataloader.action.visitor; +import java.io.InputStream; +import java.util.Map; + /** * Interface that all visitors should implement * @@ -34,10 +37,12 @@ */ public interface IVisitor { - int getNumberErrors(); + long getNumberErrors(); + + long getNumberOfRows(); - int getNumberOfRows(); + long getNumberSuccesses(); - int getNumberSuccesses(); + Map getAttachments(); } diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/InsertVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/InsertVisitor.java deleted file mode 100644 index 72faa1c48..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/InsertVisitor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.util.List; - -import org.apache.commons.beanutils.DynaBean; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.client.PartnerClient; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.sforce.ws.ConnectionException; - -/** - * @author Lexi Viripaeff - * @since 6.0 - */ -public class InsertVisitor extends PartnerLoadVisitor { - - public InsertVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, - DataWriter errorWriter) { - super(controller, monitor, successWriter, errorWriter); - } - - @Override - protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { - return client.loadInserts(dynabeans); - } - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/PartnerLoadVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/PartnerLoadVisitor.java deleted file mode 100644 index 4c4e253b0..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/PartnerLoadVisitor.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import com.salesforce.dataloader.model.Row; -import org.apache.commons.beanutils.DynaBean; - -import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.client.PartnerClient; -import com.salesforce.dataloader.config.*; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.salesforce.dataloader.exception.*; -import com.sforce.soap.partner.*; -import com.sforce.soap.partner.Error; -import com.sforce.soap.partner.fault.ApiFault; -import com.sforce.ws.ConnectionException; - -/** - * - * Base class for load visitors using the partner api client. - * - * @author Colin Jarvis - * @since 17.0 - */ -public abstract class PartnerLoadVisitor extends DAOLoadVisitor { - - public PartnerLoadVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, - DataWriter errorWriter) { - super(controller, monitor, successWriter, errorWriter); - } - - @Override - protected void loadBatch() throws DataAccessObjectException, LoadException { - Object[] results = null; - try { - results = executeClientAction(getController().getPartnerClient(), dynaArray); - } catch (ApiFault e) { - handleException(e); - } catch (ConnectionException e) { - handleException(e); - } - - // set the current processed - int currentProcessed; - try { - currentProcessed = getConfig().getInt(LastRun.LAST_LOAD_BATCH_ROW); - } catch (ParameterLoadException e) { - // if there's a problem getting last batch row, start at the beginning - currentProcessed = 0; - } - currentProcessed += results.length; - getConfig().setValue(LastRun.LAST_LOAD_BATCH_ROW, currentProcessed); - try { - getConfig().saveLastRun(); - } catch (IOException e) { - String errMsg = Messages.getString("LoadAction.errorLastRun"); - getLogger().error(errMsg, e); - handleException(errMsg, e); - } - - writeOutputToWriter(results, dataArray); - - // update Monitor - getProgressMonitor().worked(results.length); - getProgressMonitor().setSubTask(getRateCalculator().calculateSubTask(getNumberOfRows(), getNumberErrors())); - - // now clear the arrays - clearArrays(); - - } - - private void writeOutputToWriter(Object[] results, List dataArray) - throws DataAccessObjectException, LoadException { - - if (results.length != dataArray.size()) { - getLogger().fatal(Messages.getString("Visitor.errorResultsLength")); //$NON-NLS-1$ - throw new LoadException(Messages.getString("Visitor.errorResultsLength")); - } - - // have to do this because although saveResult and deleteResult - // are a) not the same class yet b) not subclassed - for (int i = 0; i < results.length; i++) { - Row dataRow = dataArray.get(i); - String statusMsg = null; - if (results instanceof SaveResult[]) { - SaveResult saveRes = (SaveResult)results[i]; - if (saveRes.getSuccess()) { - if (OperationInfo.insert == getConfig().getOperationInfo()) { - statusMsg = Messages.getString("DAOLoadVisitor.statusItemCreated"); - } else { - statusMsg = Messages.getString("DAOLoadVisitor.statusItemUpdated"); - } - } - dataRow.put(Config.STATUS_COLUMN_NAME, statusMsg); - processResult(dataRow, saveRes.getSuccess(), saveRes.getId(), saveRes.getErrors()); - } else if (results instanceof DeleteResult[]) { - DeleteResult deleteRes = (DeleteResult)results[i]; - if (deleteRes.getSuccess()) { - statusMsg = Messages.getString("DAOLoadVisitor.statusItemDeleted"); - } - dataRow.put(Config.STATUS_COLUMN_NAME, statusMsg); - processResult(dataRow, deleteRes.getSuccess(), deleteRes.getId(), deleteRes.getErrors()); - } else if (results instanceof UpsertResult[]) { - UpsertResult upsertRes = (UpsertResult)results[i]; - if (upsertRes.getSuccess()) { - statusMsg = upsertRes.getCreated() ? Messages.getString("DAOLoadVisitor.statusItemCreated") - : Messages.getString("DAOLoadVisitor.statusItemUpdated"); - } - dataRow.put(Config.STATUS_COLUMN_NAME, statusMsg); - processResult(dataRow, upsertRes.getSuccess(), upsertRes.getId(), upsertRes.getErrors()); - } - } - } - - private void processResult(Row dataRow, boolean isSuccess, String id, Error[] errors) - throws DataAccessObjectException { - // process success vs. error - // extract error message from error result - if (isSuccess) { - writeSuccess(dataRow, id, null); - } else { - writeError(dataRow, - errors == null ? Messages.getString("Visitor.noErrorReceivedMsg") : errors[0].getMessage()); - } - } - - /** - * This method performs the actual client action. It must be implemented by all subclasses. It returns an object[] - * because of saveResult[] and deleteResult[], while do the exact same thing, are two different classes without - * common inheritance. And we're stuck with it for legacy reasons. - * - * @throws ConnectionException - */ - protected abstract Object[] executeClientAction(PartnerClient client, List data) - throws ConnectionException; - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/PartnerQueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/PartnerQueryVisitor.java deleted file mode 100644 index 146744f66..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/PartnerQueryVisitor.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.*; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.salesforce.dataloader.exception.DataAccessObjectException; -import com.salesforce.dataloader.model.Row; -import com.sforce.soap.partner.QueryResult; -import com.sforce.soap.partner.sobject.SObject; -import com.sforce.ws.ConnectionException; - -/** - * Visitor to convert rows into Dynamic objects - * - * @author Lexi Viripaeff - * @author Alex Warshavsky - * @since 6.0 - */ -public class PartnerQueryVisitor extends AbstractQueryVisitor { - - private QueryResult qr; - - public PartnerQueryVisitor(Controller controller, ILoaderProgress monitor, DataWriter queryWriter, - DataWriter successWriter, DataWriter errorWriter) { - super(controller, monitor, queryWriter, successWriter, errorWriter); - } - - @Override - protected int executeQuery(String soql) throws ConnectionException { - this.qr = getQueryResult(soql); - return this.qr.getSize(); - } - - protected QueryResult getQueryResult(String soql) throws ConnectionException { - return getController().getPartnerClient().query(soql); - } - - @Override - protected void writeExtraction() throws DataAccessObjectException, ConnectionException { - while (this.qr.getRecords() != null) { - // form a map, because we aren't guaranteed to get back all the fields - final SObject[] sfdcResults = this.qr.getRecords(); - if (sfdcResults == null) { - getLogger().error(Messages.getMessage(getClass(), "errorNoResults")); - return; - } - for (int i = 0; i < sfdcResults.length; i++) { - // add row to batch - addResultRow(getDaoRow(sfdcResults[i]), sfdcResults[i].getId()); - } - if (this.qr.getDone()) { - break; - } - if (getProgressMonitor().isCanceled()) return; - this.qr = getController().getPartnerClient().queryMore(this.qr.getQueryLocator()); - } - } - - private Row getDaoRow(SObject sob) { - Row row = getMapper().mapPartnerSObjectSfdcToLocal(sob); - for (Map.Entry ent : row.entrySet()) { - Object newVal = convertFieldValue(ent.getValue()); - if (newVal != ent.getValue()) row.put(ent.getKey(), newVal); - } - return row; - } - - private static final DateFormat DF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); - private Object convertFieldValue(Object fieldVal) { - if (fieldVal instanceof Calendar) { - DF.setCalendar((Calendar)fieldVal); - return DF.format(((Calendar)fieldVal).getTime()); - } - - if (fieldVal instanceof Date) { - final DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); - df.setTimeZone(TimeZone.getTimeZone("GMT")); - return df.format((Date)fieldVal); - } - - return fieldVal; - } - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/UpdateVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/UpdateVisitor.java deleted file mode 100644 index 455d60f67..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/UpdateVisitor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.util.List; - -import org.apache.commons.beanutils.DynaBean; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.client.PartnerClient; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.sforce.ws.ConnectionException; - -/** - * @author Lexi Viripaeff - * @since 6.0 - */ -public class UpdateVisitor extends PartnerLoadVisitor { - - public UpdateVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, - DataWriter errorWriter) { - super(controller, monitor, successWriter, errorWriter); - } - - @Override - protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { - return client.loadUpdates(dynabeans); - } - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/UpsertVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/UpsertVisitor.java deleted file mode 100644 index 65d66c115..000000000 --- a/src/main/java/com/salesforce/dataloader/action/visitor/UpsertVisitor.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.action.visitor; - -import java.util.List; - -import org.apache.commons.beanutils.DynaBean; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.client.PartnerClient; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataWriter; -import com.sforce.ws.ConnectionException; - -/** - * @author Alex Warshavsky - * @since 8.0 - */ -public class UpsertVisitor extends PartnerLoadVisitor { - - public UpsertVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, - DataWriter errorWriter) { - super(controller, monitor, successWriter, errorWriter); - } - - @Override - protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { - return client.loadUpserts(dynabeans); - } - -} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/AbstractBulkQueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/AbstractBulkQueryVisitor.java new file mode 100644 index 000000000..f6c6ccb17 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/AbstractBulkQueryVisitor.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; + +import com.salesforce.dataloader.action.AbstractExtractAction; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.action.visitor.AbstractQueryVisitor; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.OperationException; +import com.salesforce.dataloader.mapping.SOQLMapper; +import com.salesforce.dataloader.model.Row; +import com.sforce.async.CSVReader; + +/** + * Query visitor for bulk api extract operations. + * + * @author Colin Jarvis + * @since 21.0 + */ +abstract public class AbstractBulkQueryVisitor extends AbstractQueryVisitor { + + public AbstractBulkQueryVisitor(AbstractExtractAction action, Controller controller, ILoaderProgress monitor, DataWriter queryWriter, + DataWriter successWriter, DataWriter errorWriter) { + super(action, controller, monitor, queryWriter, successWriter, errorWriter); + } + + protected void writeExtractionForServerStream(InputStream serverResultStream) throws IOException, DataAccessObjectException { + File bufferingFile = null; + final boolean bufferResults = getConfig().getBoolean(AppConfig.PROP_BUFFER_UNPROCESSED_BULK_QUERY_RESULTS); + OutputStream bufferingFileWriter = null; + + InputStream resultStream = serverResultStream; //read directly from server by default + try { + if (bufferResults) { + //temp csv + bufferingFile = File.createTempFile("sdl", ".csv"); + String bufferingFilePath = bufferingFile.getAbsolutePath(); + getLogger().info("Downloading result chunk to " + bufferingFilePath); + + //download results into the buffering file + int bytesRead; + byte[] buffer = new byte[8 * 1024]; + bufferingFileWriter = new FileOutputStream(bufferingFile); + while ((bytesRead = resultStream.read(buffer)) != -1) { + bufferingFileWriter.write(buffer, 0, bytesRead); + } + bufferingFileWriter.close(); + serverResultStream.close(); + + //stream from csv file + resultStream = new FileInputStream(new File(bufferingFilePath)); + } + try { + final CSVReader rdr = new CSVReader(resultStream, AppConfig.BULK_API_ENCODING); + rdr.setMaxCharsInFile(Integer.MAX_VALUE); + rdr.setMaxRowsInFile(Integer.MAX_VALUE); + List headers; + headers = rdr.nextRecord(); + List csvRow; + boolean isFirstRowInBatch = true; + while ((csvRow = rdr.nextRecord()) != null) { + final StringBuilder id = new StringBuilder(); + final Row daoRow = getDaoRow(headers, csvRow, id, isFirstRowInBatch); + addResultRow(daoRow, id.toString()); + isFirstRowInBatch = false; + } + } finally { + resultStream.close(); + } + } finally { + if (bufferingFile != null) { + bufferingFile.delete(); + } + } + } + + private Row getDaoRow(List queryResultHeaders, List csvRow, + StringBuilder id, boolean isFirstRowInBatch) throws DataAccessObjectInitializationException { + if (isFirstRowInBatch + && !getConfig().getBoolean(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS)) { + SOQLMapper mapper = (SOQLMapper)this.controller.getMapper(); + mapper.initSoqlMappingFromResultFields(queryResultHeaders); + final List daoColumns = mapper.getDaoColumnsForSoql(); + if (getConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT)) { + try { + if (this.getErrorWriter() == null) { + this.setErrorWriter(this.action.createErrorWriter()); + this.action.openErrorWriter(daoColumns); + } + if (this.getSuccessWriter() == null) { + this.setSuccessWriter(this.action.createSuccesWriter()); + this.action.openSuccessWriter(daoColumns); + } + } catch (OperationException | DataAccessObjectInitializationException e) { + throw new DataAccessObjectInitializationException(e); + } + } + } + return getMapper().mapCsvRowSfdcToLocal(queryResultHeaders, csvRow, id); + + } + +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkApiVisitorUtil.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkApiVisitorUtil.java new file mode 100644 index 000000000..d2157e708 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkApiVisitorUtil.java @@ -0,0 +1,420 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.InputStream; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.exception.ExtractException; +import com.salesforce.dataloader.exception.ExtractExceptionOnServer; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.util.DLLogManager; +import com.salesforce.dataloader.util.LoadRateCalculator; +import com.sforce.async.AsyncApiException; +import com.sforce.async.BatchInfo; +import com.sforce.async.BatchInfoList; +import com.sforce.async.BatchStateEnum; +import com.sforce.async.BulkConnection; +import com.sforce.async.CSVReader; +import com.sforce.async.ConcurrencyMode; +import com.sforce.async.ContentType; +import com.sforce.async.JobInfo; +import com.sforce.async.JobStateEnum; +import com.sforce.async.OperationEnum; + +class BulkApiVisitorUtil { + + private static final Logger logger = DLLogManager.getLogger(BulkApiVisitorUtil.class); + + private final BulkConnection connection; + + private JobInfo jobInfo = null; + private int recordsProcessed; + + private final Map attachments = new HashMap(); + private int attachmentNum; + + private final long checkStatusInterval; + private long lastStatusUpdate; + + private final ILoaderProgress monitor; + private final LoadRateCalculator rateCalc; + + private final boolean updateProgress; + + private boolean enablePKchunking = false; + private int queryChunkSize; + private String queryChunkStartRow = ""; + private AppConfig appConfig = null; + private Controller controller; + private int bulkV2LoadBatchCount = 0; + + BulkApiVisitorUtil(Controller ctl, ILoaderProgress monitor, LoadRateCalculator rateCalc, boolean updateProgress) { + this.appConfig = ctl.getAppConfig(); + this.controller = ctl; + if (isBulkV2QueryJob() || isBulkV2LoadJob()) { + this.connection = ctl.getBulkV2Client().getConnection(); + } else { + this.connection = ctl.getBulkV1Client().getConnection(); + } + + try { + // getLong will return 0 if no value is provided + long checkStatusInt = ctl.getAppConfig().getLong(AppConfig.PROP_BULK_API_CHECK_STATUS_INTERVAL); + this.checkStatusInterval = checkStatusInt > 0 ? checkStatusInt + : AppConfig.DEFAULT_BULK_API_CHECK_STATUS_INTERVAL; + } catch (ParameterLoadException e) { + throw new RuntimeException("Failed to initialize check status interval", e); + } + + /* + * ======== Start code block to support PK chunking + * + this.enablePKchunking = ctl.getConfig().getBoolean(Config.ENABLE_BULK_QUERY_PK_CHUNKING); + if (this.enablePKchunking) { + try { + int chunkSize = ctl.getConfig().getInt(Config.BULK_QUERY_PK_CHUNK_SIZE); + if (chunkSize < 1 || chunkSize > Config.MAX_BULK_QUERY_PK_CHUNK_SIZE) { + chunkSize = Config.DEFAULT_BULK_QUERY_PK_CHUNK_SIZE; + } + this.queryChunkSize = chunkSize; + } catch (ParameterLoadException e) { + throw new RuntimeException("Failed to initialize bulk query chunk size", e); + } + queryChunkStartRow = ctl.getConfig().getString(Config.BULK_QUERY_PK_CHUNK_START_ROW); + if (queryChunkStartRow == null) { + queryChunkStartRow = ""; + } + } + /* + * ======== End code block to support PK chunking + */ + this.monitor = monitor; + this.rateCalc = rateCalc; + this.updateProgress = updateProgress; + } + + BulkApiVisitorUtil(Controller ctl, ILoaderProgress monitor, LoadRateCalculator rateCalc) { + this(ctl, monitor, rateCalc, true); + } + + String getJobId() { + return this.jobInfo.getId(); + } + + public String getStagingFileInOutputStatusDir(String prefix, String suffix) { + Date currentTime = new Date(); + SimpleDateFormat format = new SimpleDateFormat("MMddyyhhmmssSSS"); //$NON-NLS-1$ + String timestamp = format.format(currentTime); + String statusOutputDir = appConfig.getString(AppConfig.PROP_OUTPUT_STATUS_DIR); + + File stagingFile = new File(statusOutputDir, prefix + timestamp + suffix); + return stagingFile.getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ + + } + + public JobInfo getJobInfo() { + return this.jobInfo; + } + + public void setJobInfo(JobInfo jinfo) { + this.jobInfo = jinfo; + } + + void createJob() throws AsyncApiException { + JobInfo job = new JobInfo(); + final OperationEnum op = this.appConfig.getOperationInfo().getBulkOperationEnum(); + job.setOperation(op); + if (op == OperationEnum.upsert) { + job.setExternalIdFieldName(this.appConfig.getString(AppConfig.PROP_IDLOOKUP_FIELD)); + } + job.setObject(this.appConfig.getString(AppConfig.PROP_ENTITY)); + ContentType jobContentType = ContentType.CSV; + if (this.appConfig.getBoolean(AppConfig.PROP_BULK_API_ZIP_CONTENT) + && op != OperationEnum.query + && !this.appConfig.isBulkV2APIEnabled()) { + // ZIP CSV content is supported only for Bulk V1 + jobContentType = ContentType.ZIP_CSV; + } + job.setContentType(jobContentType); + + ConcurrencyMode jobConcurrencyMode = ConcurrencyMode.Parallel; + if (this.appConfig.getBoolean(AppConfig.PROP_BULK_API_SERIAL_MODE) + && !this.appConfig.isBulkV2APIEnabled()) { + // Serial mode is supported only for Bulk V1 + jobConcurrencyMode = ConcurrencyMode.Serial; + } + job.setConcurrencyMode(jobConcurrencyMode); + + if (op == OperationEnum.update || op == OperationEnum.upsert || op == OperationEnum.insert) { + final String assRule = this.appConfig.getString(AppConfig.PROP_ASSIGNMENT_RULE); + if (assRule != null && (assRule.length() == 15 || assRule.length() == 18)) { + job.setAssignmentRuleId(assRule); + } + } else if (op == OperationEnum.query || op == OperationEnum.queryAll) { + if (this.enablePKchunking) { + String startRowParam = ""; + // startRow parameter of "Sforce-Enable-PKChunking" header has to be a valid + // 15 or 18 char ID. + if (this.queryChunkStartRow != null + && (this.queryChunkStartRow.length() == 15 || this.queryChunkStartRow.length() == 18)) { + startRowParam = "; startRow=" + this.queryChunkStartRow; + } + this.connection.addHeader("Sforce-Enable-PKChunking", + "chunkSize=" + this.queryChunkSize + startRowParam); + } + } + if (isBulkV2QueryJob()) { + job.setObject(this.appConfig.getString(AppConfig.PROP_EXTRACT_SOQL)); + logger.info("going to create BulkV2 query job"); + } + job = this.connection.createJob(job); + logger.info(Messages.getMessage(getClass(), "logJobCreated", job.getId())); + this.jobInfo = job; + } + + private static final NumberFormat FILE_NUM_FMT; + static { + final NumberFormat fmt = NumberFormat.getIntegerInstance(); + fmt.setGroupingUsed(false); + fmt.setMinimumIntegerDigits(3); + FILE_NUM_FMT = fmt; + } + + String addAttachment(byte[] fileContents) { + final String name = "attachment_" + FILE_NUM_FMT.format(this.attachmentNum++); + this.attachments.put(name, new ByteArrayInputStream(fileContents)); + return "#" + name; + } + + public Map getAttachments() { + return this.attachments; + } + + BatchInfo createBatch(InputStream batchContent) throws AsyncApiException { + BatchInfo batch = null; + if (isBulkV2QueryJob()) { + return null; + } else if (isBulkV2LoadJob()) { + processBulkV2LoadBatch(batchContent); + batch = new BatchInfo(); + batch.setId("BULKV2_LOAD_BATCH_" + this.bulkV2LoadBatchCount++); + } else { // Bulk v1 job + BulkConnection connectionClient = this.controller.getBulkV1Client().getConnection(); + if (this.jobInfo.getContentType() == ContentType.ZIP_CSV) { + batch = connectionClient.createBatchWithInputStreamAttachments(this.jobInfo, batchContent, this.attachments); + } else { + batch = connectionClient.createBatchFromStream(this.jobInfo, batchContent); + } + logger.info(Messages.getMessage(getClass(), "logBatchLoaded", batch.getId())); + } + + // Done creating a batch. Clear attachments map in preparation for the next batch + this.attachments.clear(); + this.attachmentNum = 0; + return batch; + } + + void processBulkV2LoadBatch(InputStream batchContent) throws AsyncApiException { + BulkV2Connection v2conn = this.controller.getBulkV2Client().getConnection(); + this.jobInfo = v2conn.startIngest(this.getJobId(), batchContent); + } + + long periodicCheckStatus() throws AsyncApiException { + if (this.monitor.isCanceled()) return 0; + final long timeRemaining = this.checkStatusInterval - (System.currentTimeMillis() - this.lastStatusUpdate); + int retryCount = 0; + int maxAttemptsCount = 0; + + try { + // limit the number of max retries in case limit is exceeded + maxAttemptsCount = 1 + Math.min(AppConfig.MAX_RETRIES_LIMIT, this.appConfig.getInt(AppConfig.PROP_MAX_RETRIES)); + } catch (ParameterLoadException e) { + maxAttemptsCount = 1 + AppConfig.DEFAULT_MAX_RETRIES; + } + if (timeRemaining <= 0) { + while (retryCount++ < maxAttemptsCount) { + try { + this.jobInfo = this.connection.getJobStatus(getJobId()); + updateJobStatus(); + return this.checkStatusInterval; + } catch (AsyncApiException ex) { + if (retryCount < maxAttemptsCount) { + try { + Thread.sleep(this.checkStatusInterval); + } catch (final InterruptedException e) {} + } else { + throw ex; + } + } + } + } + monitor.setNumberBatchesTotal(jobInfo.getNumberBatchesTotal()); + return timeRemaining; + } + + private void awaitJobCompletion() throws AsyncApiException { + long sleepTime = periodicCheckStatus(); + + while (!isJobCompleted()) { + if (this.monitor.isCanceled()) return; + try { + Thread.sleep(sleepTime); + } catch (final InterruptedException e) {} + sleepTime = periodicCheckStatus(); + } + } + + private boolean isBulkV2QueryJob() { + final OperationEnum op = this.appConfig.getOperationInfo().getBulkOperationEnum(); + return (op == OperationEnum.query || op == OperationEnum.queryAll) + && this.appConfig.isBulkV2APIEnabled(); + } + + private boolean isBulkV2LoadJob() { + final OperationEnum op = this.appConfig.getOperationInfo().getBulkOperationEnum(); + return op != OperationEnum.query && op != OperationEnum.queryAll + && this.appConfig.isBulkV2APIEnabled(); + } + + private boolean isJobCompleted() { + if (isBulkV2QueryJob() || isBulkV2LoadJob()) { + return this.jobInfo.getState() == JobStateEnum.JobComplete; + } else { // bulk v1 flavor + return this.jobInfo.getNumberBatchesQueued() == 0 + && this.jobInfo.getNumberBatchesInProgress() == 0; + } + } + + boolean hasJob() { + return this.jobInfo != null; + } + + void awaitCompletionAndCloseJob() throws AsyncApiException { + this.jobInfo = this.connection.getJobStatus(getJobId()); + updateJobStatus(); + awaitJobCompletion(); + if (!isBulkV2QueryJob() && !isBulkV2LoadJob()) { + this.jobInfo = this.connection.closeJob(getJobId()); + } + } + + private void updateJobStatus() { + if (updateProgress) { + this.monitor.worked(getNumRecordsProcessedInJob() - this.recordsProcessed); + this.monitor.setSubTask(this.rateCalc.calculateSubTask( + getNumRecordsProcessedInJob(), + getNumRecordsFailedInJob())); + } + this.recordsProcessed = getNumRecordsProcessedInJob(); + this.lastStatusUpdate = System.currentTimeMillis(); + logger.info(Messages.getMessage(getClass(), "logJobStatus", this.jobInfo.getNumberBatchesQueued(), + this.jobInfo.getNumberBatchesInProgress(), + this.jobInfo.getNumberBatchesCompleted(), + this.jobInfo.getNumberBatchesFailed())); + } + + // hack because jobInfo is not updated if an entire batch fails + private int getNumRecordsProcessedInJob() { + int numRecordsProcessedInJob = this.jobInfo.getNumberRecordsProcessed(); + if (appConfig.isBulkAPIEnabled() || appConfig.isBulkV2APIEnabled()) { + // Bulk v2 counts all processed records in the total + numRecordsProcessedInJob -= this.jobInfo.getNumberRecordsFailed(); + } + int numRecordsPerBatch = 0; + try { + numRecordsPerBatch = this.appConfig.getInt(AppConfig.PROP_IMPORT_BATCH_SIZE); + } catch (ParameterLoadException e) { + logger.warn("Incorrectly configured " + AppConfig.PROP_IMPORT_BATCH_SIZE); + } + if (numRecordsProcessedInJob == 0 && this.jobInfo.getNumberBatchesCompleted() > 0) { + numRecordsProcessedInJob = numRecordsPerBatch + * this.jobInfo.getNumberBatchesCompleted(); + } + return numRecordsProcessedInJob + getNumRecordsFailedInJob(); + } + + // hack because jobInfo is not updated if an entire batch fails + private int getNumRecordsFailedInJob() { + int numRecordsFailedInJob = this.jobInfo.getNumberRecordsFailed(); + int numRecordsPerBatch = 0; + try { + numRecordsPerBatch = this.appConfig.getInt(AppConfig.PROP_IMPORT_BATCH_SIZE); + } catch (ParameterLoadException e) { + logger.warn("Incorrectly configured " + AppConfig.PROP_IMPORT_BATCH_SIZE); + } + if (numRecordsFailedInJob == 0 && this.jobInfo.getNumberBatchesFailed() > 0) { + numRecordsFailedInJob = numRecordsPerBatch * jobInfo.getNumberBatchesFailed(); + } + return numRecordsFailedInJob; + } + BatchInfoList getBatches() throws AsyncApiException { + BulkV1Connection connectionClient = this.controller.getBulkV1Client().getConnection(); + return connectionClient.getBatchInfoList(getJobId()); + } + + CSVReader getBatchResults(String batchId) throws AsyncApiException { + BulkV1Connection connectionClient = this.controller.getBulkV1Client().getConnection(); + return new CSVReader(connectionClient.getBatchResultStream(getJobId(), batchId)); + } + + int getRecordsProcessed() throws ExtractException, AsyncApiException { + if (!isBulkV2QueryJob()) { + // Bulk v1 job. check if a batch failed, and if so, throw an ExtractException. + final BatchInfo[] batchInfoArray = getBatches().getBatchInfo(); + for (BatchInfo batchInfo : batchInfoArray) { + if (batchInfo.getState() == BatchStateEnum.Failed) { + throw new ExtractExceptionOnServer("Batch failed: " + batchInfo.getStateMessage()); + } + } + } + return getNumRecordsProcessedInJob(); + } + + void getBulkV2LoadSuccessResults(String filename) throws AsyncApiException { + this.controller.getBulkV2Client().getConnection().saveIngestSuccessResults(this.getJobId(), filename); + } + + void getBulkV2LoadErrorResults(String filename) throws AsyncApiException { + this.controller.getBulkV2Client().getConnection().saveIngestFailureResults(this.getJobId(), filename); + } + + void getBulkV2LoadUnprocessedRecords(String filename) throws AsyncApiException { + this.controller.getBulkV2Client().getConnection().saveIngestUnprocessedRecords(this.getJobId(), filename); + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkLoadVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkLoadVisitor.java new file mode 100644 index 000000000..bbf5a8f7d --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkLoadVisitor.java @@ -0,0 +1,685 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; +import java.util.TreeSet; + +import org.apache.commons.beanutils.DynaBean; +import org.apache.commons.beanutils.DynaProperty; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.client.DescribeRefObject; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataReader; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.dao.csv.CSVFileReader; +import com.salesforce.dataloader.dyna.ParentIdLookupFieldFormatter; +import com.salesforce.dataloader.exception.BatchSizeLimitException; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.LoadException; +import com.salesforce.dataloader.exception.LoadExceptionOnServer; +import com.salesforce.dataloader.exception.OperationException; +import com.salesforce.dataloader.exception.RelationshipFormatException; +import com.salesforce.dataloader.model.NACalendarValue; +import com.salesforce.dataloader.model.NADateOnlyCalendarValue; +import com.salesforce.dataloader.model.NATextValue; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; +import com.salesforce.dataloader.util.DAORowUtil; +import com.sforce.async.AsyncApiException; +import com.sforce.async.AsyncExceptionCode; +import com.sforce.async.BatchInfo; +import com.sforce.async.BatchStateEnum; +import com.sforce.async.CSVReader; + +/** + * Visitor for operations using the bulk API client + * + * @author Jesper Joergensen, Colin Jarvis + * @since 17.0 + */ +public class BulkLoadVisitor extends DAOLoadVisitor { + + private static final Logger logger = DLLogManager.getLogger(BulkLoadVisitor.class); + + private static final String SUCCESS_RESULT_COL = "Success"; + private static final String ERROR_RESULT_COL = "Error"; + private static final String ID_RESULT_COL = "Id"; + private static final String CREATED_RESULT_COL = "Created"; + + private final boolean isDelete; + private static final DateFormat DATE_FMT; + private int batchCountForJob = 0; + private List headerColumns = null; + + static { + DATE_FMT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + DATE_FMT.setTimeZone(TimeZone.getTimeZone("GMT")); + } + private final BulkApiVisitorUtil jobUtil; + private ArrayList bulkv2JobsList = new ArrayList(); + + // This keeps track of all the batches we send in order so that we know whats what when processsing results + private final List allBatchesInOrder = new ArrayList(); + + /** DataLoader uses this to help match batch results from SFDC to the rows in our input */ + private class BatchData { + final String batchId; + final int numRows; + + BatchData(String batchId, int numRows) { + this.batchId = batchId; + this.numRows = numRows; + } + } + + /** When we get batch CSV results back from sfdc they are converted into instances of RowResult */ + private static class RowResult { + RowResult(boolean success, boolean created, String id, String error) { + this.success = success; + this.created = created; + this.id = id; + this.error = error; + } + + final boolean success; + final boolean created; + final String id; + final String error; + } + + public BulkLoadVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + this.isDelete = getController().getAppConfig().getOperationInfo().isDelete(); + this.jobUtil = new BulkApiVisitorUtil(getController(), getProgressMonitor(), getRateCalculator()); + if (controller.getAppConfig().isBulkV2APIEnabled()) { + bulkv2JobsList.add(this.jobUtil); + } + } + + @Override + protected void loadBatch() throws DataAccessObjectException, OperationException, BatchSizeLimitException { + try { + if (!this.jobUtil.hasJob()) this.jobUtil.createJob(); + createBatches(); + clearArrays(); + } catch (final AsyncApiException e) { + handleException(e); + } catch (final IOException e) { + handleException(e); + } + } + + /** + * Throws a load exception + */ + @Override + protected void handleException(Throwable t) throws LoadException { + handleException(getOverrideMessage(t), t); + } + + private String getOverrideMessage(Throwable t) { + if (t instanceof AsyncApiException) { + + final AsyncApiException aae = (AsyncApiException)t; + final String hardDeleteNoPermsMessage = "hardDelete operation requires special user profile permission, please contact your system administrator"; + + if (aae.getExceptionCode() == AsyncExceptionCode.FeatureNotEnabled + && aae.getExceptionMessage().contains(hardDeleteNoPermsMessage)) + return Messages.getMessage(getClass(), "hardDeleteNoPerm"); + } + return null; + } + + private void createBatches() throws OperationException, IOException, AsyncApiException, BatchSizeLimitException { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + final PrintStream out = new PrintStream(os, true, AppConfig.BULK_API_ENCODING); + doOneBatch(out, os, this.dynaArray); + os.close(); + } + + private void doOneBatch(PrintStream out, ByteArrayOutputStream os, List rows) throws OperationException, + AsyncApiException, FileNotFoundException, BatchSizeLimitException { + int processedRecordsCount = 0; + long startTime = System.currentTimeMillis(); + long measureTime = System.currentTimeMillis(); + long elapsedTime = measureTime - startTime; + long loopStartTime = measureTime; + int timeMeasureGap = 10000; + for (int i = 0; i < rows.size(); i++) { + loopStartTime = System.currentTimeMillis(); + measureTime = loopStartTime; + final DynaBean row = rows.get(i); + if (processedRecordsCount % timeMeasureGap == 0) { + elapsedTime = System.currentTimeMillis() - measureTime; + logger.debug("rows.get() invocation time after processing 10k records is " + elapsedTime + " msec"); + } + if (processedRecordsCount == 0) { + List userColumns = getController().getDao().getColumnNames(); + headerColumns = addBatchRequestHeader(out, row, userColumns); + } + measureTime = System.currentTimeMillis(); + writeRow(row, out, headerColumns); + if (processedRecordsCount % timeMeasureGap == 0) { + elapsedTime = System.currentTimeMillis() - measureTime; + logger.debug("writeRow() invocation time after processing 10k records is " + elapsedTime + " msec"); + } + processedRecordsCount++; + if (processedRecordsCount % timeMeasureGap == 0) { + logger.debug("loop processing time after processing 10k records is " + (System.currentTimeMillis() - loopStartTime) + " msec"); + logger.debug("total processed = " + processedRecordsCount + "\n\n"); + } + } + if (processedRecordsCount > 0) { + createBatch(os, processedRecordsCount); + } + this.jobUtil.periodicCheckStatus(); + } + + private void writeRow(DynaBean row, PrintStream out, + List header) throws LoadException { + boolean notFirst = false; + for (final String sfdcColumn : header) { + if (notFirst) { + out.print(','); + } else { + notFirst = true; + } + writeSingleColumn(out, sfdcColumn, row.get(sfdcColumn)); + } + out.println(); + } + + private void writeSingleColumn(PrintStream out, String fieldName, Object fieldValue) throws LoadException { + if (fieldValue != null) { + Object col = fieldValue; + if (fieldValue instanceof NACalendarValue || fieldValue instanceof NADateOnlyCalendarValue) { + col = fieldValue.toString(); + } else if (fieldValue instanceof Calendar) { + col = DATE_FMT.format(((Calendar) fieldValue).getTime()); + } else if (fieldValue instanceof byte[]) { + if (!getController().attachmentsEnabled()) + throw new LoadException(Messages.getMessage("FinishPage", "cannotMapBase64ForBulkApi", fieldName)); + col = this.jobUtil.addAttachment((byte[])fieldValue); + } + writeColumnToCsv(out, col); + } else { + // all null values should be ignored when using bulk API + getLogger().debug(Messages.getMessage(getClass(), "noFieldVal", fieldName)); + } + } + + private void writeColumnToCsv(PrintStream out, Object val) { + out.print('"'); + out.print(val.toString().replace("\"", "\"\"")); + out.print('"'); + } + + private List addBatchRequestHeader(PrintStream serverRequestOutput, DynaBean row, List columns) + throws LoadException { + boolean first = true; + final List cols = new ArrayList(); + final Set addedCols = new TreeSet(String.CASE_INSENSITIVE_ORDER); + for (final String userColumn : columns) { + final String sfdcColList = getMapper().getMapping(userColumn, false, true); + // if the column is not mapped, don't send it + if (sfdcColList == null || sfdcColList.length() == 0) { + // TODO: we should make it more obvious to users when we omit a column + getLogger().warn("Cannot find mapping for column: " + userColumn + ". Omitting column"); + continue; + } + String[] sfdcColArray = sfdcColList.split(AppUtil.COMMA); + for (String sfdcColumn : sfdcColArray) { + sfdcColumn = sfdcColumn.strip(); + // TODO we don't really need to be this strict about a delete CSV file.. as long as the IDS are there + if (this.isDelete && (!first || !"id".equalsIgnoreCase(sfdcColumn))) + throw new LoadException(Messages.getMessage(getClass(), "deleteCsvError")); + addFieldToBatchRequestHeader(serverRequestOutput, sfdcColumn, cols, addedCols, first); + if (first) first = false; + } + } + + // Handle constant field mappings in the field mapping file (.sdl) + for (DynaProperty dynaProperty : row.getDynaClass().getDynaProperties()) { + final String name = dynaProperty.getName(); + if (row.get(name) != null && !addedCols.contains(name)) { + addFieldToBatchRequestHeader(serverRequestOutput, name, cols, addedCols, first); + } + } + serverRequestOutput.println(); + return Collections.unmodifiableList(cols); + } + + private void addFieldToBatchRequestHeader(PrintStream serverRequestOutput, String sfdcColumn, List cols, Set addedCols, + boolean first) { + if (!first) { + serverRequestOutput.print(','); + } + // Make sure that relationship field header is in the formats + // specified at https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/datafiles_csv_rel_field_header_row.htm + // Format for Polymorphic relations: ObjectType:RelationshipName.IndexedFieldName + // Format for single parent type relations: RelationshipName.IndexedFieldName + String sfdcColumnForBulk = sfdcColumn; + try { + ParentIdLookupFieldFormatter relField = new ParentIdLookupFieldFormatter(sfdcColumn); + DescribeRefObject parentRef = getController().getPartnerClient().getReferenceDescribes().getParentSObject(sfdcColumn); + int numParentTypes = 1; + if (parentRef != null + && parentRef.getChildField() != null + && parentRef.getChildField().getReferenceTo() != null) { + numParentTypes = parentRef.getChildField().getReferenceTo().length; + } + if (relField.getParent().getParentObjectName() == null || numParentTypes == 1) { + if (relField.getParentFieldName() == null) { + sfdcColumnForBulk = relField.getParent().getRelationshipName(); + } else { + sfdcColumnForBulk = relField.getParent().getRelationshipName() + + "." + relField.getParentFieldName(); + } + } else { + sfdcColumnForBulk = relField.getParent().getParentObjectName() + + ":" + relField.getParent().getRelationshipName() + + "." + relField.getParentFieldName(); + } + + } catch (RelationshipFormatException ex) { + // ignore + } + serverRequestOutput.print(sfdcColumnForBulk); + cols.add(sfdcColumn); + addedCols.add(sfdcColumn); + } + + private String writeServerLoadBatchDataToCSV(ByteArrayOutputStream os) { + String filenamePrefix = "uploadedToServer"; + String filename = generateBatchCSVFilename(filenamePrefix, batchCountForJob); + File uploadedToServerCSVFile = new File(filename); + final byte[] request = os.toByteArray(); + try { + FileOutputStream outputStream = new FileOutputStream(uploadedToServerCSVFile); + outputStream.write(request); + outputStream.close(); + } catch (Exception ex) { + logger.info("unable to create file " + filename); + } + return filename; + } + + private void writeRawResultsToCSV(CSVReader serverResultsReader, int batchNum) { + String filenamePrefix = "rawResultsFromServer"; + String filename = generateBatchCSVFilename(filenamePrefix, batchNum); + File rawBatchResultsCSVFile = new File(filename); + try { + FileOutputStream outputStream = new FileOutputStream(rawBatchResultsCSVFile); + PrintStream printOutput = new PrintStream(outputStream); + List row = serverResultsReader.nextRecord(); + while (row != null && !row.isEmpty()) { + int cellIdx = 0; + for (String cell : row) { + if (cellIdx != 0) { + printOutput.print(", "); + } + cellIdx++; + printOutput.print(cell); + } + printOutput.println(""); + row = serverResultsReader.nextRecord(); + } + printOutput.close(); + outputStream.close(); + } catch (Exception ex) { + logger.info("unable to create file " + filename); + } + } + + private String generateBatchCSVFilename(String prefix, int batchNum) { + String successResultsFilename = controller.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + int parentDirLocation = successResultsFilename.lastIndexOf(System.getProperty("file.separator")); + String resultsDir = successResultsFilename.substring(0, parentDirLocation); + return resultsDir + + System.getProperty("file.separator") + + prefix + + "_Batch" + batchNum + "_" + + controller.getFormattedCurrentTimestamp() + ".csv"; + } + + private void createBatch(ByteArrayOutputStream os, int numRecords) throws AsyncApiException, FileNotFoundException { + if (numRecords <= 0) return; + final byte[] request = os.toByteArray(); + logger.info("upload request size in bytes: " + request.length); + String uploadDataFileName = null; + if (controller.getAppConfig().getBoolean(AppConfig.PROP_SAVE_BULK_SERVER_LOAD_AND_RAW_RESULTS_IN_CSV)) { + this.batchCountForJob++; + uploadDataFileName = writeServerLoadBatchDataToCSV(os); + } + BatchInfo bi = null; + if (uploadDataFileName != null) { + bi = this.jobUtil.createBatch(new FileInputStream(uploadDataFileName)); + } else { + bi = this.jobUtil.createBatch(new ByteArrayInputStream(request, 0, request.length)); + } + os.reset(); + this.allBatchesInOrder.add(new BatchData(bi.getId(), numRecords)); + } + + @Override + public void flushRemaining() throws OperationException, DataAccessObjectException, BatchSizeLimitException { + super.flushRemaining(); + if (this.jobUtil.hasJob()) { + try { + this.jobUtil.awaitCompletionAndCloseJob(); + } catch (final AsyncApiException e) { + logger.warn("Failed to close job", e); + } + try { + if (this.getConfig().isBulkAPIEnabled() || this.getConfig().isBulkV2APIEnabled()) + getResults(); + } catch (AsyncApiException e) { + throw new LoadException("Failed to get batch results", e); + } + } + } + + private void getBulkV2LoadJobResults() throws AsyncApiException, OperationException, DataAccessObjectException { + this.getSuccessWriter().close(); + this.getErrorWriter().close(); + + AppConfig appConfig = this.getConfig(); + String successWriterFile = appConfig.getString(AppConfig.PROP_OUTPUT_SUCCESS); + String errorWriterFile = appConfig.getString(AppConfig.PROP_OUTPUT_ERROR); + // TODO for unprocessed records. Also uncomment in Controller.java to set the right value + // for Config.OUTPUT_UNPROCESSED_RECORDS + // String unprocessedRecordsWriterFile = config.getString(Config.OUTPUT_UNPROCESSED_RECORDS); + + this.jobUtil.getBulkV2LoadSuccessResults(successWriterFile); + CSVFileReader csvReader = new CSVFileReader(new File(successWriterFile), appConfig, true, false); + this.setSuccesses(csvReader.getTotalRows()); + + this.jobUtil.getBulkV2LoadErrorResults(errorWriterFile); + csvReader = new CSVFileReader(new File(errorWriterFile), appConfig, true, false); + this.setErrors(csvReader.getTotalRows()); + } + + private void getResults() throws AsyncApiException, OperationException, DataAccessObjectException { + + getProgressMonitor().setSubTask(Messages.getMessage(getClass(), "retrievingResults")); + + if (this.getConfig().isBulkV2APIEnabled()) { + getBulkV2LoadJobResults(); + return; + } + DataReader dataReader = null; + dataReader = resetDAO(); + + // create a map of batch infos by batch id. Each batchinfo has the final processing state of the batch + final Map batchInfoMap = createBatchInfoMap(); + + // go through all the batches we sent to sfdc in the same order and process the batch results for + // each one by looking them up in batchInfoMap + this.batchCountForJob = 0; + int uploadedRowCount = 0; + for (final BatchData clientBatchInfo : this.allBatchesInOrder) { + processResults(dataReader, + batchInfoMap.get(clientBatchInfo.batchId), clientBatchInfo, uploadedRowCount); + uploadedRowCount += clientBatchInfo.numRows; + } + } + + + private int firstDAORowForCurrentBatch = 0; + + private void processResults(final DataReader dataReader, final BatchInfo batch, + BatchData clientBatchInfo, final int firstRowInBatch) + throws LoadException, DataAccessObjectException, AsyncApiException { + // For Bulk API, we don't save any success or error until the end, + // so we have to go through the original CSV from the beginning while + // we go through the results from the server. + // TODO we should save the ACTUAL rows/batches sent to the server + + // do some basic checks to make sure we are matching up the batches correctly + sanityCheckBatch(clientBatchInfo, batch); + + // If there was an error processing the batch and the state isn't 'Completed' get stateMessage from batch + final String stateMessage = (batch.getState() == BatchStateEnum.Completed) ? null : batch.getStateMessage(); + final String errorMessage = stateMessage == null ? null : Messages.getMessage(getClass(), "batchError", + stateMessage); + + int lastRowInCurrentBatch = firstRowInBatch + clientBatchInfo.numRows - 1; + int lastDAORowForCurrentBatch = this.batchRowToDAORowList.get(lastRowInCurrentBatch); + + final int totalRowsInDAOInCurrentBatch = lastDAORowForCurrentBatch - this.firstDAORowForCurrentBatch + 1; + List rows; + rows = dataReader.readTableRowList(totalRowsInDAOInCurrentBatch); + if (batch.getState() == BatchStateEnum.Completed || batch.getNumberRecordsProcessed() > 0) { + try { + processBatchResults(batch, errorMessage, batch.getState(), rows, this.firstDAORowForCurrentBatch); + } catch (IOException e) { + throw new LoadException("IOException while reading batch results", e); + } + } else { + for (final TableRow row : rows) { + writeError(row, errorMessage); + } + } + // update to process the next batch + this.firstDAORowForCurrentBatch = lastDAORowForCurrentBatch + 1; + } + + private void processBatchResults(final BatchInfo batch, final String errorMessage, + final BatchStateEnum state, final List rows, final int firstDataReaderRowInBatch) throws DataAccessObjectException, IOException, AsyncApiException { + + // get the batch csv result stream from sfdc + final CSVReader resultRdr = this.jobUtil.getBatchResults(batch.getId()); + + if (controller.getAppConfig().getBoolean(AppConfig.PROP_SAVE_BULK_SERVER_LOAD_AND_RAW_RESULTS_IN_CSV)) { + this.batchCountForJob++; + writeRawResultsToCSV(this.jobUtil.getBatchResults(batch.getId()), this.batchCountForJob); + } + // read in the result csv header and note the column indices + Map hdrIndices = mapHeaderIndices(resultRdr.nextRecord()); + final int successIdx = hdrIndices.get(SUCCESS_RESULT_COL); + final int createdIdx = isDelete ? -1 : hdrIndices.get(CREATED_RESULT_COL); + final int idIdx = hdrIndices.get(ID_RESULT_COL); + final int errIdx = hdrIndices.get(ERROR_RESULT_COL); + hdrIndices = null; + + for (final TableRow row : rows) { + boolean conversionSuccessOfRow = isRowConversionSuccessful(); + if (!conversionSuccessOfRow) { + continue; // this DAO row failed to convert and was not part of the batch sent to the server. Go to the next one + } + final List res = resultRdr.nextRecord(); + + // no result for this column. In this case it failed, and we should use the batch state message + if (state == BatchStateEnum.Failed || errorMessage != null) { + getLogger().warn( + Messages.getMessage(getClass(), "logBatchInfoWithMessage", batch.getId(), state, errorMessage)); + writeError(row, errorMessage); + } else if (res == null || res.isEmpty()) { + String msg = Messages.getMessage(getClass(), "noResultForRow", row.toString(), batch.getId()); + writeError(row, msg); + getLogger().warn(msg); + } else { + // convert the row into a RowResults so its easy to inspect + final RowResult rowResult = new RowResult(Boolean.valueOf(res.get(successIdx)), isDelete ? false + : Boolean.valueOf(res.get(createdIdx)), res.get(idIdx), res.get(errIdx)); + writeRowResult(row, rowResult); + } + } + } + + // returns a map of batchinfos indexed by batch id + private Map createBatchInfoMap() throws AsyncApiException { + Map batchInfoMap = new HashMap(); + for (BatchInfo bi : this.jobUtil.getBatches().getBatchInfo()) { + batchInfoMap.put(bi.getId(), bi); + } + return batchInfoMap; + } + + private DataReader resetDAO() throws DataAccessObjectInitializationException, LoadException { + final DataReader dataReader = (DataReader)getController().getDao(); + dataReader.close(); + // TODO: doing this causes sql to be executed twice, for sql we should cache results in a local file + dataReader.open(); + // when re-opening the dao we need to start at the same row in the input + DAORowUtil.get().skipRowToStartOffset(getConfig(), dataReader, getProgressMonitor(), true); + return dataReader; + } + + private void writeRowResult(TableRow row, RowResult resultRow) throws DataAccessObjectException { + if (resultRow.success) { + String successMessage; + switch (getConfig().getOperationInfo()) { + case hard_delete: + successMessage = "statusItemHardDeleted";//$NON-NLS-1$ + break; + case delete: + successMessage = "statusItemDeleted";//$NON-NLS-1$ + break; + default: + successMessage = resultRow.created ? "statusItemCreated"//$NON-NLS-1$ + : "statusItemUpdated"; //$NON-NLS-1$ + } + writeSuccess(row, resultRow.id, Messages.getMessage(getClass(), successMessage)); + } else { + writeError(row, parseAsyncApiError(resultRow.error)); + } + } + + // creates a map from the header strings in the result csv to the Integer index in the list + private Map mapHeaderIndices(final List header) { + final Map indices = new HashMap(); + for (int i = 0; i < header.size(); i++) + indices.put(header.get(i), i); + return indices; + } + + private void sanityCheckBatch(BatchData clientBatchInfo, BatchInfo batch) throws LoadException { + final String batchId = clientBatchInfo.batchId; + + assert (batchId != null && batchId.equals(batch.getId())); + assert (jobUtil.getJobId().equals(batch.getJobId())); + assert clientBatchInfo.numRows > 0; + final BatchStateEnum state = batch.getState(); + + if (state != BatchStateEnum.Completed && state != BatchStateEnum.Failed) + sanityCheckError(batchId, "Expected batch state to be Completed or Failed, but was " + state); + } + + private void sanityCheckError(String id, String errMsg) throws LoadException { + throw new LoadExceptionOnServer(id + ": " + errMsg); + } + + private String parseAsyncApiError(final String errString) { + final String sep = ":"; + final String suffix = "--"; + final int lastSep = errString.lastIndexOf(sep); + if (lastSep > 0 && errString.endsWith(suffix)) { + final String fields = errString.substring(lastSep + 1, errString.length() - suffix.length()); + final String start = errString.substring(0, lastSep); + if (fields != null && fields.length() > 0) + return new StringBuilder(start).append("\n").append("Error fields: ").append(fields).toString(); + return start; + } + return errString; + } + + @Override + protected void convertBulkAPINulls(TableRow row) { + for (String columnName : row.getHeader().getColumns()) { + if (NATextValue.isNA(row.get(columnName))) { + row.put(columnName, NATextValue.getInstance()); + } + } + } + + @Override + protected void conversionFailed(TableRow row, String errMsg) throws DataAccessObjectException, + OperationException { + super.conversionFailed(row, errMsg); + getLogger().warn("Skipping results for row " + row + " which failed before upload to Saleforce.com"); + } + + @Override + public Map getAttachments() { + return this.jobUtil.getAttachments(); + } + + @Override + protected int getMaxBytesInBatch() { + return this.getConfig().isBulkV2APIEnabled() ? AppConfig.MAX_BULKV2_API_IMPORT_JOB_BYTES : AppConfig.MAX_BULK_API_IMPORT_BATCH_BYTES; + } + + @Override + protected int getBytesInBean(DynaBean dynaBean) { + final ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ps; + int beanBytes = 0; + + try { + ps = new PrintStream(os, true, AppConfig.BULK_API_ENCODING); + if (headerColumns == null) { + List userColumns = getController().getDao().getColumnNames(); + headerColumns = addBatchRequestHeader(ps, dynaBean, userColumns); + } + writeRow(dynaBean, ps, headerColumns); + beanBytes = os.size(); + ps.close(); + os.close(); + } catch (IOException | LoadException e) { + logger.warn("unable to determine row size"); + } + return beanBytes; + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV1Connection.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV1Connection.java new file mode 100644 index 000000000..a2ac4cc91 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV1Connection.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.client.ClientBase; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.client.HttpTransportInterface; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.exception.HttpClientTransportException; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.async.AsyncApiException; +import com.sforce.async.AsyncExceptionCode; +import com.sforce.async.BatchInfoList; +import com.sforce.async.BulkConnection; +import com.sforce.async.ContentType; +import com.sforce.async.JobInfo; +import com.sforce.async.QueryResultList; +import com.sforce.ws.ConnectorConfig; +import com.sforce.ws.parser.PullParserException; +import com.sforce.ws.parser.XmlInputStream; + +public class BulkV1Connection extends BulkConnection { + private static Logger logger = DLLogManager.getLogger(BulkV1Connection.class); + + public BulkV1Connection(ConnectorConfig config) throws AsyncApiException { + super(config); + + // This is needed to set the correct client name in Bulk V1 calls + addHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER, config.getRequestHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER)); + } + + public void addHeader(String headerName, String headerValue) { + super.addHeader(headerName, headerValue); + if (ClientBase.SFORCE_CALL_OPTIONS_HEADER.equalsIgnoreCase(headerName)) { + logger.debug(ClientBase.SFORCE_CALL_OPTIONS_HEADER + " : " + headerValue); + } + } + + public JobInfo getJobStatus(String jobId) throws AsyncApiException { + return getJobStatus(jobId, ContentType.XML); + } + + public JobInfo getJobStatus(String jobId, ContentType contentType) throws AsyncApiException { + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_USE_LEGACY_HTTP_GET)) { + return super.getJobStatus(jobId, contentType); + } else { + String[] urlParts = {"job", jobId}; + InputStream in = invokeBulkV1GET(urlParts); + return processBulkV1Get(in, contentType, JobInfo.class); + } + } + + public BatchInfoList getBatchInfoList(String jobId) throws AsyncApiException { + return getBatchInfoList(jobId, ContentType.XML); + } + + public BatchInfoList getBatchInfoList(String jobId, ContentType contentType) throws AsyncApiException { + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_USE_LEGACY_HTTP_GET)) { + return super.getBatchInfoList(jobId, contentType); + } else { + String[] urlParts = {"job", jobId, "batch"}; + InputStream in = invokeBulkV1GET(urlParts); + return processBulkV1Get(in, contentType, BatchInfoList.class); + } + } + + public InputStream getBatchResultStream(String jobId, String batchId) throws AsyncApiException { + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_USE_LEGACY_HTTP_GET)) { + return super.getBatchResultStream(jobId, batchId); + } else { + String[] urlParts = {"job", jobId, "batch", batchId, "result"}; + return invokeBulkV1GET(urlParts); + } + } + + public QueryResultList getQueryResultList(String jobId, String batchId) throws AsyncApiException { + return getQueryResultList(jobId, batchId, ContentType.XML); + } + + public QueryResultList getQueryResultList(String jobId, String batchId, ContentType contentType) throws AsyncApiException { + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_USE_LEGACY_HTTP_GET)) { + return super.getQueryResultList(jobId, batchId, contentType); + } else { + InputStream in = getBatchResultStream(jobId, batchId); + return processBulkV1Get(in, contentType, QueryResultList.class); + } + } + + public InputStream getQueryResultStream(String jobId, String batchId, String resultId) throws AsyncApiException { + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_USE_LEGACY_HTTP_GET)) { + return super.getQueryResultStream(jobId, batchId, resultId); + } else { + String[] urlParts = {"job", jobId, "batch", batchId, "result", resultId}; + return invokeBulkV1GET(urlParts); + } + } + + private InputStream invokeBulkV1GET(String[] urlParts) throws AsyncApiException { + String endpoint = getConfig().getRestEndpoint(); + endpoint = endpoint.endsWith("/") ? endpoint : endpoint + "/"; + if (urlParts != null) { + for (String urlPart : urlParts) { + endpoint += urlPart + "/"; + } + } + try { + HttpTransportInterface transport = HttpClientTransport.getInstance(); + transport.setConfig(getConfig()); + return transport.httpGet(endpoint); + } catch (IOException | HttpClientTransportException e) { + logger.error(e.getMessage()); + throw new AsyncApiException("Failed to get result ", AsyncExceptionCode.ClientInputError, e); + } + } + + @SuppressWarnings("unchecked") + private T processBulkV1Get(InputStream is, ContentType contentType, Class returnClass) throws AsyncApiException { + try { + if (contentType == ContentType.JSON || contentType == ContentType.ZIP_JSON) { + return AppUtil.deserializeJsonToObject(is, returnClass); + } else { + XmlInputStream xin = new XmlInputStream(); + xin.setInput(is, "UTF-8"); + Constructor[] ctors = returnClass.getDeclaredConstructors(); + Constructor ctor = null; + for (int i = 0; i < ctors.length; i++) { + ctor = ctors[i]; + if (ctor.getGenericParameterTypes().length == 0) + break; + } + if (ctor == null) { + throw new AsyncApiException("Failed to get result: " + returnClass + " cannot be instantiated", AsyncExceptionCode.ClientInputError); + } + T result = (T)ctor.newInstance(); + Method loadMethod = returnClass.getMethod("load", xin.getClass(), typeMapper.getClass()); + loadMethod.invoke(result, xin, typeMapper); + return result; + } + } catch (IOException | PullParserException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | InstantiationException e) { + logger.error(e.getMessage()); + throw new AsyncApiException("Failed to get result ", AsyncExceptionCode.ClientInputError, e); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV1QueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV1QueryVisitor.java new file mode 100644 index 000000000..0ebfaaa72 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV1QueryVisitor.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.ByteArrayInputStream; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; + +import com.salesforce.dataloader.action.AbstractExtractAction; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.ExtractException; +import com.salesforce.dataloader.exception.ExtractExceptionOnServer; +import com.salesforce.dataloader.exception.OperationException; +import com.sforce.async.AsyncApiException; +import com.sforce.async.BatchInfo; +import com.sforce.async.BatchStateEnum; +import com.sforce.async.QueryResultList; + +/** + * Query visitor for bulk api extract operations. + * + * @author Colin Jarvis + * @since 21.0 + */ +public class BulkV1QueryVisitor extends AbstractBulkQueryVisitor { + + private BatchInfo[] batches; + + public BulkV1QueryVisitor(AbstractExtractAction action, Controller controller, ILoaderProgress monitor, DataWriter queryWriter, + DataWriter successWriter, DataWriter errorWriter) { + super(action, controller, monitor, queryWriter, successWriter, errorWriter); + } + + @Override + protected int executeQuery(String soql) throws AsyncApiException, OperationException { + final BulkApiVisitorUtil jobUtil = new BulkApiVisitorUtil(getController(), getProgressMonitor(), + getRateCalculator(), false); + jobUtil.createJob(); + try { + jobUtil.createBatch(new ByteArrayInputStream(soql.getBytes(AppConfig.BULK_API_ENCODING))); + } catch (final UnsupportedEncodingException e) { + throw new ExtractException(e); + } + jobUtil.awaitCompletionAndCloseJob(); + if (!this.getConfig().isBulkV2APIEnabled()) { + this.batches = jobUtil.getBatches().getBatchInfo(); + } + return jobUtil.getRecordsProcessed(); + } + + @Override + protected void writeExtraction() throws AsyncApiException, ExtractException, DataAccessObjectException { + for (BatchInfo b : this.batches) { + writeExtractionForBatch(b); + } + } + private void writeExtractionForBatch(BatchInfo batch) throws AsyncApiException, ExtractException, DataAccessObjectException { + if (batch.getState() == BatchStateEnum.Failed) + throw new ExtractExceptionOnServer("Batch failed: " + batch.getStateMessage()); + final QueryResultList results = getController().getBulkV1Client().getConnection() + .getQueryResultList(batch.getJobId(), batch.getId()); + + for (final String resultId : results.getResult()) { + if (getProgressMonitor().isCanceled()) return; + try { + final InputStream serverResultStream = getController().getBulkV1Client().getConnection() + .getQueryResultStream(batch.getJobId(), batch.getId(), resultId); + writeExtractionForServerStream(serverResultStream); + } catch (final IOException e) { + throw new ExtractExceptionOnServer(e); + } + } + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV2Connection.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV2Connection.java new file mode 100644 index 000000000..68369d758 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV2Connection.java @@ -0,0 +1,624 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/********************************** + * A code snippet showing Bulk v2 Ingest using BulkV2Connection. + * Requires dataloader--uber.jar in the classpath to compile. + * + +import java.net.URL; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.io.IOException; + +import com.salesforce.dataloader.action.visitor.BulkV2Connection; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.sforce.async.JobInfo; +import com.sforce.async.JobStateEnum; +import com.sforce.async.OperationEnum; +import com.sforce.soap.partner.Connector; +import com.sforce.soap.partner.LoginResult; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.ws.ConnectorConfig; + +public class TestBulkV2 { + public static void main(String[] args) { + String insertFilename = "./insertAccountCsv.csv"; + String deleteFilename = "./deleteAccountCsv.csv"; + String successFilename = "./ingestSuccessResults.csv"; + failureFilename = "./ingestFailureResults.csv"; + String unprocessedFilename = "./ingestUnprocessedRecords.csv"; + String bulkQueryResultsFilename = "./queryResults.csv"; + String username = ""; + String password = ""; + static final String myDomainURLString = "https://.my.salesforce.com"; + static final String restEndpoint = myDomainURLString + "/services/data/v52.0/jobs/"; + + try { + URL DEFAULT_AUTH_ENDPOINT_URL = new URL(Connector.END_POINT); + URL serverUrl = new URL(myDomainURLString); + + ConnectorConfig cc = new ConnectorConfig(); + cc.setTransport(HttpClientTransport.class); + cc.setUsername(username); + cc.setPassword(password); + cc.setAuthEndpoint(serverUrl + DEFAULT_AUTH_ENDPOINT_URL.getPath()); + cc.setServiceEndpoint(serverUrl + DEFAULT_AUTH_ENDPOINT_URL.getPath()); + cc.setRestEndpoint(restEndpoint); + final PartnerConnection conn = Connector.newConnection(cc); + + // bulkv2 insert + BulkV2Connection v2conn = new BulkV2Connection(cc); + JobInfo job = executeJob("account", OperationEnum.insert, v2conn, insertFilename); + v2conn.saveIngestSuccessResults(job.getId(), successFilename); + v2conn.saveIngestFailureResults(job.getId(), failureFilename); + v2conn.saveIngestUnprocessedRecords(job.getId(), unprocessedFilename); + + // bulkv2 query + job = new JobInfo(); + job.setOperation(OperationEnum.query); + job.setObject("account"); + job.setContentType(ContentType.CSV); + job.setObject("select id from Account"); + job = v2conn.createJob(job); + // wait for the job to complete + while (job.getState() != JobStateEnum.JobComplete) { + Thread.sleep(10,000); + job = v2conn.getExtractJobStatus(job.getId()); + } + // download query results + BufferedOutputStream csvFileStream = new BufferedOutputStream(new FileOutputStream(bulkQueryResultsFilename)); + String locator = v2conn.getQueryLocator(); + while (!"null".equalsIgnoreCase(locator)) { + BufferedInputStream resultsStream = new BufferedInputStream(v2conn.getQueryResultStream(job.getId(), locator)); + writeTo(resultsStream, csvFileStream); + resultsStream.close(); + locator = v2conn.getQueryLocator(); + } + csvFileStream.close(); + } catch (Exception ex) { + ex.printStackTrace(); + System.exit(-1); + } + } + + private static JobInfo executeJob(String objectName, OperationEnum operation, + BulkV2Connection v2conn, String ingestFilename) throws Exception { + JobInfo job = new JobInfo(); + job.setObject(objectName); + job.setOperation(operation); + job = v2conn.createJob(job); + job = v2conn.startIngest(job.getId(), ingestFilename); + while (job.getState() != JobStateEnum.JobComplete) { + Thread.sleep(10,000); + job = v2conn.getIngestJobStatus(job.getId()); + } + return job; + } + + private static void writeTo(BufferedInputStream bis, BufferedOutputStream bos) throws IOException { + byte[] buffer = new byte[2048]; + for(int len; (len = bis.read(buffer)) > 0;) { + bos.write(buffer, 0, len); + } + } +} + **************************/ + +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.Serializable; +import java.io.FileOutputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.apache.http.Header; +import org.apache.http.HttpResponse; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.client.ClientBase; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.client.HttpTransportInterface; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.exception.HttpClientTransportException; +import com.salesforce.dataloader.util.AppUtil; +import com.salesforce.dataloader.util.AppUtil.OSType; +import com.sforce.async.AsyncApiException; +import com.sforce.async.AsyncExceptionCode; +import com.sforce.async.BulkConnection; +import com.sforce.async.ContentType; +import com.sforce.async.JobInfo; +import com.sforce.async.JobStateEnum; +import com.sforce.async.OperationEnum; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; +import com.sforce.ws.parser.PullParserException; +import com.sforce.ws.parser.XmlInputStream; + +enum HttpMethod { + GET, + POST, + PATCH, + PUT +} + +public class BulkV2Connection extends BulkConnection { + private static final String URI_STEM_QUERY = "query/"; + private static final String URI_STEM_INGEST = "ingest/"; + private static final String AUTH_HEADER = "Authorization"; + private static final String REQUEST_CONTENT_TYPE_HEADER = "Content-Type"; + private static final String ACCEPT_CONTENT_TYPES_HEADER = "ACCEPT"; + private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer "; + private static final String UTF_8 = StandardCharsets.UTF_8.name(); + private static final String INGEST_RESULTS_SUCCESSFUL = "successfulResults"; + private static final String INGEST_RESULTS_UNSUCCESSFUL = "failedResults"; + private static final String INGEST_RECORDS_UNPROCESSED = "unprocessedrecords"; + + private String queryLocator = ""; + private int numberOfRecordsInQueryResult = 0; + private HashMap headers = new HashMap(); + private Controller controller = null; + + private static Logger logger = DLLogManager.getLogger(BulkV2Connection.class); + + /********************************** + * + * public, common methods + * + **********************************/ + public BulkV2Connection(ConnectorConfig connectorConfig, Controller controller) throws AsyncApiException { + super(connectorConfig); + this.controller = controller; + } + + public JobInfo getJobStatus(String jobId) throws AsyncApiException { + return getJobStatus(jobId, ContentType.JSON); + } + + public JobInfo closeJob(String jobId) throws AsyncApiException { + return getJobStatus(jobId); + } + + public JobInfo getJobStatus(String jobId, ContentType contentType) throws AsyncApiException { + String urlString = constructRequestURL(jobId); + HashMap headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE); + // there is nothing in the request body. + return doSendJobRequestToServer(urlString, + headers, + HttpMethod.GET, + ContentType.JSON, + null, + true, + "Failed to get job status for job " + jobId); + } + + public JobInfo abortJob(String jobId, boolean isQuery) throws AsyncApiException { + return setJobState(jobId, isQuery, JobStateEnum.Aborted, "Failed to abort job " + jobId); + } + + public JobInfo setJobState(String jobId, boolean isQuery, JobStateEnum state, String errorMessage) throws AsyncApiException { + String urlString = constructRequestURL(jobId); + HashMap headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE); + HashMap requestBodyMap = new HashMap(); + requestBodyMap.put("state", state.toString()); + + return doSendJobRequestToServer(urlString, + headers, + HttpMethod.PATCH, + ContentType.JSON, + requestBodyMap, + true, + errorMessage); + } + + /********************************** + * + * public, extract (aka query) methods + * + **********************************/ + public JobInfo getExtractJobStatus(String jobId) throws AsyncApiException { + return getJobStatus(jobId); + } + + public InputStream getQueryResultStream(String jobId, String locator) throws AsyncApiException { + String urlString = constructRequestURL(jobId) + "results/"; + if (locator != null && !locator.isEmpty() && !"null".equalsIgnoreCase(locator)) { + urlString += "?locator=" + locator; + } + try { + return doGetQueryResultStream(new URL(urlString), getHeaders(JSON_CONTENT_TYPE, CSV_CONTENT_TYPE)); + } catch (IOException | ConnectionException e) { + throw new AsyncApiException("Failed to get query results for job " + jobId, AsyncExceptionCode.ClientInputError, e); + } + } + + public String getQueryLocator() { + return this.queryLocator; + } + + public int getNumberOfRecordsInQueryResult() { + return this.numberOfRecordsInQueryResult; + } + + /********************************** + * + * public, ingest (create, update, upsert, delete) methods + * + **********************************/ + // needed for all upload operations (non-query operations) + public JobInfo startIngest(String jobId, InputStream bulkUploadStream) throws AsyncApiException { + String urlString = constructRequestURL(jobId) + "batches/"; + HashMap headers = getHeaders(CSV_CONTENT_TYPE, JSON_CONTENT_TYPE); + try { + HttpClientTransport clientTransport = HttpClientTransport.getInstance(); + clientTransport.setConfig(getConfig()); + clientTransport.connect(urlString, headers, false, HttpTransportInterface.SupportedHttpMethodType.PUT, bulkUploadStream, CSV_CONTENT_TYPE); + + // Following is needed to actually send the request to the server + InputStream serverResponseStream = clientTransport.getContent(); + if (!clientTransport.isSuccessful()) { + parseAndThrowException(serverResponseStream, ContentType.JSON); + } + }catch (IOException e) { + throw new AsyncApiException("Failed to upload to server for job " + jobId, AsyncExceptionCode.ClientInputError, e); + } + + // Mark upload as completed + urlString = constructRequestURL(jobId); + headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE); + + setJobState(jobId, false, JobStateEnum.UploadComplete, "Failed to mark completion of the upload"); + return getIngestJobStatus(jobId); + } + + public JobInfo getIngestJobStatus(String jobId) throws AsyncApiException { + return getJobStatus(jobId); + } + + public void saveIngestSuccessResults(String jobId, String filename) throws AsyncApiException { + doSaveIngestResults(jobId, filename, INGEST_RESULTS_SUCCESSFUL, false); + } + + public void saveIngestFailureResults(String jobId, String filename) throws AsyncApiException { + doSaveIngestResults(jobId, filename, INGEST_RESULTS_UNSUCCESSFUL, true); + } + + public void saveIngestUnprocessedRecords(String jobId, String filename) throws AsyncApiException { + doSaveIngestResults(jobId, filename, INGEST_RECORDS_UNPROCESSED, false); + } + + public InputStream getIngestSuccessResultsStream(String jobId) throws AsyncApiException { + return doGetIngestResultsStream(jobId, INGEST_RESULTS_SUCCESSFUL); + } + + public InputStream getIngestFailedResultsStream(String jobId) throws AsyncApiException { + return doGetIngestResultsStream(jobId, INGEST_RESULTS_UNSUCCESSFUL); + } + + public InputStream getIngestUnprocessedRecordsStream(String jobId) throws AsyncApiException { + return doGetIngestResultsStream(jobId, INGEST_RECORDS_UNPROCESSED); + } + + public void addHeader(String headerName, String headerValue) { + headers.put(headerName, headerValue); + } + + /********************************** + * + * private, common methods + * + **********************************/ + private String constructRequestURL(String jobId) { + String urlString = getConfig().getRestEndpoint(); + if (jobId == null) { + jobId = ""; + } + boolean isExtraction = controller.getAppConfig().getOperationInfo().isExtraction(); + if (isExtraction) { + urlString += URI_STEM_QUERY + jobId + "/"; + } else { + urlString += URI_STEM_INGEST + jobId + "/"; + } + return urlString; + } + + public JobInfo createJob(JobInfo job) throws AsyncApiException { + ContentType type = job.getContentType(); + if (type != null && type != ContentType.CSV) { + throw new AsyncApiException("Unsupported Content Type", AsyncExceptionCode.FeatureNotEnabled); + } + OperationEnum operation = job.getOperation(); + String urlString = constructRequestURL(job.getId()); + HashMapheaders = null; + + HashMap requestBodyMap = new HashMap(); + requestBodyMap.put("operation", job.getOperation().toString()); + if (controller.getAppConfig().getOperationInfo().isExtraction()) { + headers = getHeaders(JSON_CONTENT_TYPE, CSV_CONTENT_TYPE); + requestBodyMap.put("query", job.getObject()); + } else { + headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE); + requestBodyMap.put("object", job.getObject()); + requestBodyMap.put("contentType", type.toString()); + if (AppUtil.getOSType() == OSType.WINDOWS) { + requestBodyMap.put("lineEnding", "CRLF"); + } else { + requestBodyMap.put("lineEnding", "LF"); + } + if (operation.equals(OperationEnum.upsert)) { + requestBodyMap.put("externalIdFieldName", job.getExternalIdFieldName()); + } + if (operation.equals(OperationEnum.upsert) + || operation.equals(OperationEnum.insert) + || operation.equals(OperationEnum.update)) { + if (job.getAssignmentRuleId() != null && !job.getAssignmentRuleId().isBlank()) { + requestBodyMap.put("assignmentRuleId", job.getAssignmentRuleId()); + } + } + } + return doSendJobRequestToServer(urlString, + headers, + HttpMethod.POST, + ContentType.JSON, + requestBodyMap, + true, + "Failed to create job"); + } + + private JobInfo doSendJobRequestToServer(String urlString, + HashMap headers, + HttpMethod requestMethod, + ContentType responseContentType, + HashMap requestBodyMap, + boolean processServerResponse, + String exceptionMessageString) throws AsyncApiException + { + if (headers == null) { + headers = getHeaders(JSON_CONTENT_TYPE, JSON_CONTENT_TYPE); + } + try { + InputStream in = null; + boolean successfulRequest = true; + HttpClientTransport transport = HttpClientTransport.getInstance(); + transport.setConfig(getConfig()); + if (requestMethod == HttpMethod.GET) { + if (requestBodyMap != null && !requestBodyMap.isEmpty()) { + Set paramNameSet = requestBodyMap.keySet(); + boolean firstParam = true; + for (Object paramName : paramNameSet) { + if (firstParam) { + urlString += "?" + paramName.toString() + "=" + requestBodyMap.get(paramName); + firstParam = false; + } else { + urlString += "&" + paramName.toString() + "=" + requestBodyMap.get(paramName); + } + } + } + // make a get request + try { + in = transport.httpGet(urlString); + } catch (HttpClientTransportException ex) { + parseAndThrowException(ex); + } + } else { + OutputStream out; + if (requestMethod == HttpMethod.PATCH) { + out = transport.connect(urlString, headers, true, HttpTransportInterface.SupportedHttpMethodType.PATCH); + } else if (requestMethod == HttpMethod.PUT) { + out = transport.connect(urlString, headers, true, HttpTransportInterface.SupportedHttpMethodType.PUT); + } else { // assume post method + out = transport.connect(urlString, headers, true, HttpTransportInterface.SupportedHttpMethodType.POST); + } + String requestContent = AppUtil.serializeToJson(requestBodyMap); + out.write(requestContent.getBytes(UTF_8)); + out.close(); + in = transport.getContent(); + successfulRequest = transport.isSuccessful(); + } + if (!processServerResponse) { + // sent the request to server, return without processing the response + return null; + } + JobInfo result = null; + if (successfulRequest) { + if (responseContentType == ContentType.ZIP_XML || responseContentType == ContentType.XML) { + XmlInputStream xin = new XmlInputStream(); + xin.setInput(in, UTF_8); + result = new JobInfo(); + result.load(xin, typeMapper); + } else { + result = AppUtil.deserializeJsonToObject(in, JobInfo.class); + } + } else { + parseAndThrowException(in, responseContentType); + } + return result; + } catch (IOException e) { + throw new AsyncApiException(exceptionMessageString, AsyncExceptionCode.ClientInputError, e); + } catch (ConnectionException e) { + throw new AsyncApiException(exceptionMessageString, AsyncExceptionCode.ClientInputError, e); + } catch (PullParserException e) { + throw new AsyncApiException(exceptionMessageString, AsyncExceptionCode.ClientInputError, e); + } + } + + private static void parseAndThrowException(HttpClientTransportException ex) throws AsyncApiException { + ContentType type = null; + String contentTypeHeader = ex.getConnection().getContentType(); + if (contentTypeHeader != null) { + if (contentTypeHeader.contains(XML_CONTENT_TYPE)) { + type = ContentType.XML; + } else if (contentTypeHeader.contains(JSON_CONTENT_TYPE)) { + type = ContentType.JSON; + } + } + parseAndThrowException(ex.getInputStream(), type); + } + + static void parseAndThrowException(InputStream is, ContentType type) throws AsyncApiException { + try { + AsyncApiException exception; + + BulkV2Error[] errorList = AppUtil.deserializeJsonToObject(is, BulkV2Error[].class); + if (errorList[0].message.contains("Aggregate Relationships not supported in Bulk Query")) { + exception = new AsyncApiException(errorList[0].message, AsyncExceptionCode.FeatureNotEnabled); + } else { + exception = new AsyncApiException(errorList[0].errorCode + " : " + errorList[0].message, AsyncExceptionCode.Unknown); + } + throw exception; + } catch (IOException | NullPointerException e) { + throw new AsyncApiException("Failed to parse exception", AsyncExceptionCode.ClientInputError, e); + } + } + + private HashMap getHeaders(String requestContentType, String acceptContentType) { + HashMap newMap = new HashMap(); + String authHeaderValue = AUTH_HEADER_VALUE_PREFIX + getConfig().getSessionId(); + newMap.put(REQUEST_CONTENT_TYPE_HEADER, requestContentType); + newMap.put(ACCEPT_CONTENT_TYPES_HEADER, acceptContentType); + newMap.put(AUTH_HEADER, authHeaderValue); + newMap.put(ClientBase.SFORCE_CALL_OPTIONS_HEADER, getConfig().getRequestHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER)); + logger.debug(ClientBase.SFORCE_CALL_OPTIONS_HEADER + " : " + getConfig().getRequestHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER)); + for (Map.Entry entry : headers.entrySet()) { + newMap.put(entry.getKey(), entry.getValue()); + } + return newMap; + } + + /********************************** + * + * private, extract (aka query) methods + * @throws ConnectionException + * + **********************************/ + private InputStream doGetQueryResultStream(URL resultsURL, HashMap headers) throws IOException, AsyncApiException, ConnectionException { + InputStream is = null; + try { + HttpClientTransport transport = HttpClientTransport.getInstance(); + transport.setConfig(getConfig()); + is = transport.httpGet(resultsURL.toString()); + HttpResponse httpResponse = transport.getHttpResponse(); + if (httpResponse != null) { + Header header = httpResponse.getFirstHeader("Sforce-Locator"); + if (header != null) { + this.queryLocator = header.getValue(); + } + header = httpResponse.getFirstHeader("Sforce-NumberOfRecords"); + if (header != null) { + this.numberOfRecordsInQueryResult = Integer.valueOf(header.getValue()); + } + } + } catch (HttpClientTransportException ex) { + parseAndThrowException(ex); + } + return is; + } + + /********************************** + * + * private, ingest methods + * @throws AsyncApiException + * + **********************************/ + + private InputStream doGetIngestResultsStream(String jobId, String resultsType) throws AsyncApiException { + String resultsURLString = constructRequestURL(jobId) + resultsType; + InputStream is = null; + try { + HttpClientTransport transport = HttpClientTransport.getInstance(); + transport.setConfig(getConfig()); + is = transport.httpGet(resultsURLString); + } catch (IOException e) { + throw new AsyncApiException("Failed to get " + resultsType + " for job id " + jobId, AsyncExceptionCode.ClientInputError, e); + } catch (HttpClientTransportException e) { + parseAndThrowException(e); + } + return is; + } + + private void doSaveIngestResults(String jobId, String filename, String resultsType, boolean append) throws AsyncApiException { + BufferedOutputStream bos; + try { + bos = new BufferedOutputStream(new FileOutputStream(filename, append)); + } catch (FileNotFoundException e) { + throw new AsyncApiException("File " + filename + " not found", AsyncExceptionCode.ClientInputError, e); + } + BufferedInputStream bis = new BufferedInputStream(doGetIngestResultsStream(jobId, resultsType)); + try { + byte[] buffer = new byte[2048]; + boolean firstLineSkipped = !append; + for(int len; (len = bis.read(buffer)) > 0;) { + if (!firstLineSkipped) { + String str = new String(buffer); + if (str.contains("\n")) { + String[] parts = str.split("\n"); + if (parts.length > 1) { + str = ""; + for (int i = 1; i < parts.length; i++) { + if (i > 1) { + str += System.lineSeparator(); + } + str += parts[i]; + } + buffer = str.getBytes(controller.getAppConfig().getCsvEncoding(true)); + int counter = 0; + while(counter < buffer.length && buffer[counter] != 0) { + counter++; + } + len = counter; + firstLineSkipped = true; + } + } + } + if (firstLineSkipped) { + bos.write(buffer, 0, len); + } + } + bis.close(); + bos.flush(); + bos.close(); + } catch (IOException e) { + throw new AsyncApiException("Failed to get " + resultsType + " for job " + jobId, AsyncExceptionCode.ClientInputError, e); + } + } +} + +class BulkV2Error implements Serializable { + private static final long serialVersionUID = 3L; + public String errorCode = ""; + public String message = ""; +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV2QueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV2QueryVisitor.java new file mode 100644 index 000000000..2e0ebc32e --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/bulk/BulkV2QueryVisitor.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.bulk; + +import java.io.IOException; +import java.io.InputStream; + +import com.salesforce.dataloader.action.AbstractExtractAction; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.ExtractException; +import com.salesforce.dataloader.exception.OperationException; +import com.sforce.async.AsyncApiException; + + +/** + * Query visitor for bulk api extract operations. + * + * @author Colin Jarvis + * @since 21.0 + */ +public class BulkV2QueryVisitor extends AbstractBulkQueryVisitor { + + private String jobId; + + public BulkV2QueryVisitor(AbstractExtractAction action, Controller controller, ILoaderProgress monitor, DataWriter queryWriter, + DataWriter successWriter, DataWriter errorWriter) { + super(action, controller, monitor, queryWriter, successWriter, errorWriter); + } + + @Override + protected int executeQuery(String soql) throws AsyncApiException, OperationException { + final BulkApiVisitorUtil jobUtil = new BulkApiVisitorUtil(getController(), getProgressMonitor(), + getRateCalculator(), false); + jobUtil.createJob(); + this.jobId = jobUtil.getJobId(); + jobUtil.awaitCompletionAndCloseJob(); + return jobUtil.getRecordsProcessed(); + } + + @Override + protected void writeExtraction() throws AsyncApiException, ExtractException, DataAccessObjectException { + BulkV2Connection v2Conn = getController().getBulkV2Client().getConnection(); + try { + InputStream serverResultStream = v2Conn.getQueryResultStream(this.jobId, ""); + writeExtractionForServerStream(serverResultStream); + String locator = v2Conn.getQueryLocator(); + while (!"null".equalsIgnoreCase(locator)) { + serverResultStream = v2Conn.getQueryResultStream(this.jobId, locator); + writeExtractionForServerStream(serverResultStream); + locator = v2Conn.getQueryLocator(); + } + } catch (final IOException e) { + throw new ExtractException(e); + } + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerDeleteVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerDeleteVisitor.java new file mode 100644 index 000000000..51f569c75 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerDeleteVisitor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +/** + * @author Lexi Viripaeff + * @since 6.0 + */ +public class PartnerDeleteVisitor extends PartnerLoadVisitor { + + public PartnerDeleteVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + @Override + protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { + return client.loadDeletes(dynabeans); + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerInsertVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerInsertVisitor.java new file mode 100644 index 000000000..7d31cb01f --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerInsertVisitor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +/** + * @author Lexi Viripaeff + * @since 6.0 + */ +public class PartnerInsertVisitor extends PartnerLoadVisitor { + + public PartnerInsertVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + @Override + protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { + return client.loadInserts(dynabeans); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerLoadVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerLoadVisitor.java new file mode 100644 index 000000000..acd10930a --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerLoadVisitor.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.util.List; + +import com.salesforce.dataloader.model.TableRow; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.config.*; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.exception.*; +import com.sforce.soap.partner.DeleteResult; +import com.sforce.soap.partner.OwnerChangeOption; +import com.sforce.soap.partner.OwnerChangeOptionType; +import com.sforce.soap.partner.SaveResult; +import com.sforce.soap.partner.UndeleteResult; +import com.sforce.soap.partner.UpsertResult; +import com.sforce.soap.partner.fault.ApiFault; +import com.sforce.ws.ConnectionException; + +/** + * + * Base class for load visitors using the partner api client. + * + * @author Colin Jarvis + * @since 17.0 + */ +public abstract class PartnerLoadVisitor extends DAOLoadVisitor { + + public PartnerLoadVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + @Override + protected void loadBatch() throws DataAccessObjectException, LoadException { + Object[] results = null; + setHeaders(); + try { + results = executeClientAction(getController().getPartnerClient(), dynaArray); + } catch (ApiFault e) { + handleException(e); + } catch (ConnectionException e) { + handleException(e); + } + + writeOutputToWriter(results); + setLastRunProperties(results); + + // update Monitor + getProgressMonitor().worked(results.length); + getProgressMonitor().setSubTask(getRateCalculator().calculateSubTask(getNumberOfRows(), getNumberErrors())); + + // now clear the arrays + clearArrays(); + + } + + private void setHeaders() { + setKeepAccountTeamHeader(); + } + + private void setKeepAccountTeamHeader() { + AppConfig appConfig = this.controller.getAppConfig(); + OwnerChangeOption keepAccountTeamOption = new OwnerChangeOption(); + OwnerChangeOption[] ownerChangeOptionArray; + if (appConfig.getBoolean(AppConfig.PROP_PROCESS_KEEP_ACCOUNT_TEAM) + && "Account".equalsIgnoreCase(appConfig.getString(AppConfig.PROP_ENTITY))) { + // Support for Keeping Account keepAccountTeam during Account ownership change + // More details at https://developer.salesforce.com/docs/atlas.en-us.api.meta/api/sforce_api_header_ownerchangeoptions.htm + keepAccountTeamOption.setExecute(true); + keepAccountTeamOption.setType(OwnerChangeOptionType.KeepAccountTeam); // Transfer Open opportunities owned by the account's owner + ownerChangeOptionArray = new OwnerChangeOption[] {keepAccountTeamOption}; + } else { + // clear ownerChangeOptions from the existing connection otherwise. + ownerChangeOptionArray = new OwnerChangeOption[] {}; + } + this.controller.getPartnerClient().getConnection().setOwnerChangeOptions(ownerChangeOptionArray); + } + + private void writeOutputToWriter(Object[] results) + throws DataAccessObjectException, LoadException { + + // have to do this because although saveResult and deleteResult + // are a) not the same class yet b) not subclassed + int batchRowCounter = 0; + for (int i = 0; i < this.daoRowList.size(); i++) { + TableRow daoRow = this.daoRowList.get(i); + if (!isRowConversionSuccessful()) { + continue; + } + String statusMsg = null; + if (results instanceof SaveResult[]) { + SaveResult saveRes = (SaveResult)results[batchRowCounter]; + if (saveRes.getSuccess()) { + if (OperationInfo.insert == getConfig().getOperationInfo()) { + statusMsg = Messages.getString("DAOLoadVisitor.statusItemCreated"); + } else { + statusMsg = Messages.getString("DAOLoadVisitor.statusItemUpdated"); + } + } + daoRow.put(AppConfig.STATUS_COLUMN_NAME, statusMsg); + processResult(daoRow, saveRes.getSuccess(), saveRes.getId(), saveRes.getErrors()); + } else if (results instanceof DeleteResult[]) { + DeleteResult deleteRes = (DeleteResult)results[batchRowCounter]; + if (deleteRes.getSuccess()) { + statusMsg = Messages.getString("DAOLoadVisitor.statusItemDeleted"); + } + daoRow.put(AppConfig.STATUS_COLUMN_NAME, statusMsg); + processResult(daoRow, deleteRes.getSuccess(), deleteRes.getId(), deleteRes.getErrors()); + } else if (results instanceof UndeleteResult[]) { + UndeleteResult undeleteRes = (UndeleteResult)results[batchRowCounter]; + if (undeleteRes.getSuccess()) { + statusMsg = Messages.getString("DAOLoadVisitor.statusItemUndeleted"); + } + daoRow.put(AppConfig.STATUS_COLUMN_NAME, statusMsg); + processResult(daoRow, undeleteRes.getSuccess(), undeleteRes.getId(), undeleteRes.getErrors()); + } else if (results instanceof UpsertResult[]) { + UpsertResult upsertRes = (UpsertResult)results[batchRowCounter]; + if (upsertRes.getSuccess()) { + statusMsg = upsertRes.getCreated() ? Messages.getString("DAOLoadVisitor.statusItemCreated") + : Messages.getString("DAOLoadVisitor.statusItemUpdated"); + } + daoRow.put(AppConfig.STATUS_COLUMN_NAME, statusMsg); + processResult(daoRow, upsertRes.getSuccess(), upsertRes.getId(), upsertRes.getErrors()); + } + batchRowCounter++; + if (results.length < batchRowCounter) { + getLogger().fatal(Messages.getString("Visitor.errorResultsLength")); //$NON-NLS-1$ + throw new LoadException(Messages.getString("Visitor.errorResultsLength")); + } + } + if (results.length > batchRowCounter) { + getLogger().fatal(Messages.getString("Visitor.errorResultsLength")); //$NON-NLS-1$ + throw new LoadException(Messages.getString("Visitor.errorResultsLength")); + } + } + + /** + * This method performs the actual client action. It must be implemented by all subclasses. It returns an object[] + * because of saveResult[] and deleteResult[], while do the exact same thing, are two different classes without + * common inheritance. And we're stuck with it for legacy reasons. + * + * @throws ConnectionException + */ + protected abstract Object[] executeClientAction(PartnerClient client, List data) + throws ConnectionException; + + @Override + protected int getMaxBytesInBatch() { + return AppConfig.MAX_SOAP_API_IMPORT_BATCH_BYTES; + } + @Override + protected int getBytesInBean(DynaBean dynaBean) { + return dynaBean.toString().length(); + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/PartnerQueryAllVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerQueryAllVisitor.java similarity index 86% rename from src/main/java/com/salesforce/dataloader/action/visitor/PartnerQueryAllVisitor.java rename to src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerQueryAllVisitor.java index fbe9575a1..574964e5d 100644 --- a/src/main/java/com/salesforce/dataloader/action/visitor/PartnerQueryAllVisitor.java +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerQueryAllVisitor.java @@ -24,8 +24,9 @@ * POSSIBILITY OF SUCH DAMAGE. */ -package com.salesforce.dataloader.action.visitor; +package com.salesforce.dataloader.action.visitor.partner; +import com.salesforce.dataloader.action.AbstractExtractAction; import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataWriter; @@ -40,9 +41,9 @@ */ public class PartnerQueryAllVisitor extends PartnerQueryVisitor { - public PartnerQueryAllVisitor(Controller controller, ILoaderProgress monitor, DataWriter queryWriter, + public PartnerQueryAllVisitor(AbstractExtractAction action, Controller controller, ILoaderProgress monitor, DataWriter queryWriter, DataWriter successWriter, DataWriter errorWriter) { - super(controller, monitor, queryWriter, successWriter, errorWriter); + super(action, controller, monitor, queryWriter, successWriter, errorWriter); } @Override diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerQueryVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerQueryVisitor.java new file mode 100644 index 000000000..db63acfab --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerQueryVisitor.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.*; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.action.visitor.AbstractQueryVisitor; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.OperationException; +import com.salesforce.dataloader.mapping.SOQLMapper; +import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.action.AbstractExtractAction; +import com.sforce.soap.partner.QueryResult; +import com.sforce.soap.partner.sobject.SObject; +import com.sforce.ws.ConnectionException; + +/** + * Visitor to convert rows into Dynamic objects + * + * @author Lexi Viripaeff + * @author Alex Warshavsky + * @since 6.0 + */ +public class PartnerQueryVisitor extends AbstractQueryVisitor { + + private QueryResult qr; + private final Logger logger; + + public PartnerQueryVisitor(AbstractExtractAction action, Controller controller, ILoaderProgress monitor, DataWriter queryWriter, + DataWriter successWriter, DataWriter errorWriter) { + super(action, controller, monitor, queryWriter, successWriter, errorWriter); + this.logger = DLLogManager.getLogger(getClass()); + } + + @Override + protected int executeQuery(String soql) throws ConnectionException { + this.qr = getQueryResult(soql); + return this.qr.getSize(); + } + + protected QueryResult getQueryResult(String soql) throws ConnectionException { + return getController().getPartnerClient().query(soql); + } + + @Override + protected void writeExtraction() throws DataAccessObjectException, ConnectionException { + while (this.qr.getRecords() != null) { + // form a map, because we aren't guaranteed to get back all the fields + final SObject[] sfdcResults = this.qr.getRecords(); + if (sfdcResults == null) { + getLogger().error(Messages.getMessage(getClass(), "errorNoResults")); + return; + } + for (int i = 0; i < sfdcResults.length; i++) { + // add row to batch + addResultRow(getDaoRow(sfdcResults[i], i==0), sfdcResults[i].getId()); + } + if (this.qr.getDone()) { + break; + } + if (getProgressMonitor().isCanceled()) return; + this.qr = getController().getPartnerClient().queryMore(this.qr.getQueryLocator()); + } + } + + private Row getDaoRow(SObject sob, boolean firstRowInBatch) { + if (firstRowInBatch + && !this.controller.getAppConfig().getBoolean(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS)) { + // header field is not set in the mapper + Row row = getMapper().mapPartnerSObjectSfdcToLocal(sob); + try { + List queryResultFieldsList; + queryResultFieldsList = row.getColumnNames(); + SOQLMapper mapper = (SOQLMapper)this.controller.getMapper(); + mapper.initSoqlMappingFromResultFields(queryResultFieldsList); + final List daoColumns = mapper.getDaoColumnsForSoql(); + // setting DAO's column names forces output to be restricted to the provided field names + ((DataWriter)controller.getDao()).setColumnNames(daoColumns); + if (getConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT)) { + try { + if (this.getErrorWriter() == null) { + this.setErrorWriter(this.action.createErrorWriter()); + this.action.openErrorWriter(daoColumns); + } + if (this.getSuccessWriter() == null) { + this.setSuccessWriter(this.action.createSuccesWriter()); + this.action.openSuccessWriter(daoColumns); + } + } catch (OperationException e) { + throw new DataAccessObjectInitializationException(e); + } + } + } catch (DataAccessObjectInitializationException e) { + // TODO Auto-generated catch block + logger.warn("Unable to map query result fields to DAO columns"); + } + } + Row row = getMapper().mapPartnerSObjectSfdcToLocal(sob); + for (Map.Entry ent : row.entrySet()) { + Object newVal = convertFieldValue(ent.getValue()); + if (newVal != ent.getValue()) row.put(ent.getKey(), newVal); + } + return row; + } + + private static final DateFormat DF = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + private Object convertFieldValue(Object fieldVal) { + if (fieldVal instanceof Calendar) { + DF.setCalendar((Calendar)fieldVal); + return DF.format(((Calendar)fieldVal).getTime()); + } + + if (fieldVal instanceof Date) { + final DateFormat df = new SimpleDateFormat("yyyy-MM-dd"); + df.setTimeZone(TimeZone.getTimeZone("GMT")); + return df.format((Date)fieldVal); + } + + return fieldVal; + } + +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUndeleteVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUndeleteVisitor.java new file mode 100644 index 000000000..65eeb3c3d --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUndeleteVisitor.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +/** + * @author Lexi Viripaeff + * @since 6.0 + */ +public class PartnerUndeleteVisitor extends PartnerLoadVisitor { + + public PartnerUndeleteVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + @Override + protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { + return client.loadUndeletes(dynabeans); + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUpdateVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUpdateVisitor.java new file mode 100644 index 000000000..382b28309 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUpdateVisitor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +/** + * @author Lexi Viripaeff + * @since 6.0 + */ +public class PartnerUpdateVisitor extends PartnerLoadVisitor { + + public PartnerUpdateVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + @Override + protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { + return client.loadUpdates(dynabeans); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUpsertVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUpsertVisitor.java new file mode 100644 index 000000000..3be8bb06a --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/partner/PartnerUpsertVisitor.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.partner; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +/** + * @author Alex Warshavsky + * @since 8.0 + */ +public class PartnerUpsertVisitor extends PartnerLoadVisitor { + + public PartnerUpsertVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + @Override + protected Object[] executeClientAction(PartnerClient client, List dynabeans) throws ConnectionException { + return client.loadUpserts(dynabeans); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTConnection.java b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTConnection.java new file mode 100644 index 000000000..e7879d9a4 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTConnection.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.action.visitor.rest; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.commons.beanutils.DynaBean; +import org.apache.commons.io.IOUtils; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.client.HttpTransportInterface; +import com.salesforce.dataloader.client.SessionInfo; +import com.salesforce.dataloader.client.CompositeRESTClient.ACTION_ENUM; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dyna.SforceDynaBean; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.async.AsyncApiException; +import com.sforce.soap.partner.SaveResult; +import com.sforce.soap.partner.StatusCode; +import com.sforce.soap.partner.fault.ApiFault; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; + +public class RESTConnection { + private ConnectorConfig connectorConfig; + private Controller controller; + private static Logger logger = DLLogManager.getLogger(RESTConnection.class); + + public RESTConnection(ConnectorConfig config, Controller controller) throws AsyncApiException { + this.connectorConfig = config; + this.controller = controller; + } + + + @SuppressWarnings("unchecked") + public SaveResult[] loadAction(SessionInfo session, ACTION_ENUM action, List dynabeans) throws ConnectionException { + String actionStr = "update"; // default + switch (action) { + case DELETE: + actionStr = "delete"; + break; + default: + actionStr = "update"; + break; + } + logger.debug(Messages.getFormattedString("Client.beginOperation", actionStr)); //$NON-NLS-1$ + ConnectionException connectionException = null; + try { + Map batchRecords = this.getSobjectMapForCompositeREST(dynabeans, "update"); + String json = ""; + try { + json = AppUtil.serializeToJson(batchRecords); + logger.debug("JSON for batch update using Composite REST:\n" + json); + } catch (JsonProcessingException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + throw new ConnectionException(e.getMessage()); + } + + HashMap headers = new HashMap(); + headers.put("Content-Type", "application/JSON"); + headers.put("ACCEPT", "application/JSON"); + headers.put("Authorization", "Bearer " + session.getSessionId()); + String lookupFieldName = AppConfig.getCurrentConfig().getString(AppConfig.PROP_IDLOOKUP_FIELD); + if (lookupFieldName == null || lookupFieldName.isBlank()) { + lookupFieldName = "id"; + } + if (!"id".equalsIgnoreCase(lookupFieldName) + && Controller.getAPIMajorVersion() < 61) { + String message = "update operation does not support referencing objects using a non-id field such as " + + lookupFieldName + " for API version 60 or lower. The current API version is " + + Controller.getAPIVersion(); + logger.error(message); + throw new ConnectionException(message); + } + HttpClientTransport transport = HttpClientTransport.getInstance(); + transport.setConfig(connectorConfig); + + // assume update operation by default and set http method value to PATCH + HttpTransportInterface.SupportedHttpMethodType httpMethod = HttpTransportInterface.SupportedHttpMethodType.PATCH; + if (action == ACTION_ENUM.DELETE) { + httpMethod = HttpTransportInterface.SupportedHttpMethodType.DELETE; + } + try { + OutputStream out = transport.connect( + connectorConfig.getRestEndpoint() + + controller.getAppConfig().getString(AppConfig.PROP_ENTITY) + + "/" + lookupFieldName + "/" + + "?updateOnly=true", + headers, + true, + httpMethod); + out.write(json.getBytes(StandardCharsets.UTF_8.name())); + out.close(); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new ConnectionException(e.getMessage()); + } + InputStream in = null; + try { + in = transport.getContent(); + } catch (IOException e) { + logger.error(e.getMessage()); + throw new ConnectionException(e.getMessage()); + } + boolean successfulRequest = transport.isSuccessful(); + ArrayList resultList = new ArrayList(); + if (successfulRequest) { + Object[] jsonResults = null; + try { + jsonResults = AppUtil.deserializeJsonToObject(in, Object[].class); + } catch (IOException e) { + logger.warn("Composite REST returned no results - " + e.getMessage()); + throw new ConnectionException(e.getMessage()); + } + for (Object result : jsonResults) { + Map resultMap = (Map)result; + SaveResult resultToSave = new SaveResult(); + resultToSave.setId((String)resultMap.get("id")); + resultToSave.setSuccess(((Boolean)resultMap.get("success")).booleanValue()); + List> errorResultsArray = (List>)resultMap.get("errors"); + ArrayList errorList = new ArrayList(); + if (errorResultsArray != null) { + for (Map errorMap : errorResultsArray) { + com.sforce.soap.partner.Error error = new com.sforce.soap.partner.Error(); + String codeStr = StatusCode.valuesToEnums.get((String)errorMap.get("statusCode")); + StatusCode statusCode = StatusCode.valueOf(codeStr); + error.setStatusCode(statusCode); + error.setMessage((String) errorMap.get("message")); + List fieldsList = (List) errorMap.get("fields"); + error.setFields(fieldsList.toArray(new String[1])); + errorList.add(error); + } + resultToSave.setErrors(errorList.toArray(new com.sforce.soap.partner.Error[1])); + } + resultList.add(resultToSave); + } + } else { + try { + String resultStr = IOUtils.toString(in, StandardCharsets.UTF_8); + logger.warn(resultStr); + } catch (IOException e) { + logger.warn(e.getMessage()); + } + } + session.performedSessionActivity(); // reset session activity timer + return resultList.toArray(new SaveResult[0]); + } catch (ConnectionException ex) { + logger.error( + Messages.getFormattedString( + "Client.operationError", new String[]{actionStr, ex.getMessage()}), ex); //$NON-NLS-1$ + if (ex instanceof ApiFault) { + ApiFault fault = (ApiFault)ex; + String faultMessage = fault.getExceptionMessage(); + logger.error( + Messages.getFormattedString( + "Client.operationError", new String[]{actionStr, faultMessage}), fault); //$NON-NLS-1$ + } + connectionException = ex; + } + throw connectionException; + } + + private Map getSobjectMapForCompositeREST(List dynaBeans, String opName) { + try { + List> sobjectList = SforceDynaBean.getRESTSObjectArray(controller, dynaBeans, controller.getAppConfig().getString(AppConfig.PROP_ENTITY), + controller.getAppConfig().getBoolean(AppConfig.PROP_INSERT_NULLS)); + HashMap recordsMap = new HashMap(); + recordsMap.put("records", sobjectList); + recordsMap.put("allOrNone", false); + return recordsMap; + } catch (IllegalAccessException ex) { + logger.error( + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ + throw new RuntimeException(ex); + } catch (InvocationTargetException ex) { + logger.error( + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ + throw new RuntimeException(ex); + } catch (NoSuchMethodException ex) { + logger.error( + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ + throw new RuntimeException(ex); + } catch (ParameterLoadException ex) { + logger.error( + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ + throw new RuntimeException(ex); + } + } + +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTDeleteVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTDeleteVisitor.java new file mode 100644 index 000000000..546af134c --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTDeleteVisitor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.rest; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.CompositeRESTClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +public class RESTDeleteVisitor extends RESTLoadVisitor { + + public RESTDeleteVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + protected Object[] executeClientAction(CompositeRESTClient client, List dynabeans) throws ConnectionException { + return client.loadAction(CompositeRESTClient.ACTION_ENUM.DELETE, dynabeans); + } +} diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTLoadVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTLoadVisitor.java new file mode 100644 index 000000000..5dcfa6f75 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTLoadVisitor.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.action.visitor.rest; + + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.client.CompositeRESTClient; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.LoadException; +import com.salesforce.dataloader.exception.OperationException; +import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; +import com.sforce.soap.partner.SaveResult; +import com.sforce.soap.partner.fault.ApiFault; +import com.sforce.ws.ConnectionException; + +public abstract class RESTLoadVisitor extends DAOLoadVisitor { + + public RESTLoadVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + protected void loadBatch() throws DataAccessObjectException, OperationException { + Object[] results = null; + setHeaders(); + try { + // executeClientAction() is implemented by concrete subclasses + results = executeClientAction(getController().getRESTClient(), dynaArray); + } catch (ApiFault e) { + handleException(e); + } catch (ConnectionException e) { + handleException(e); + } + + writeOutputToWriter(results); + setLastRunProperties(results); + + // update Monitor + getProgressMonitor().worked(results.length); + getProgressMonitor().setSubTask(getRateCalculator().calculateSubTask(getNumberOfRows(), getNumberErrors())); + + // now clear the arrays + clearArrays(); + } + + private void writeOutputToWriter(Object[] results) + throws DataAccessObjectException, LoadException { + // have to do this because although saveResult and deleteResult + // are a) not the same class yet b) not subclassed + int batchRowCounter = 0; + for (int i = 0; i < this.daoRowList.size(); i++) { + TableRow daoRow = this.daoRowList.get(i); + if (!isRowConversionSuccessful()) { + continue; + } + String statusMsg = null; + if (results instanceof SaveResult[]) { + SaveResult saveRes = (SaveResult)results[batchRowCounter]; + if (saveRes.getSuccess()) { + if (OperationInfo.insert == getConfig().getOperationInfo()) { + statusMsg = Messages.getString("DAOLoadVisitor.statusItemCreated"); + } else { + statusMsg = Messages.getString("DAOLoadVisitor.statusItemUpdated"); + } + } + daoRow.put(AppConfig.STATUS_COLUMN_NAME, statusMsg); + processResult(daoRow, saveRes.getSuccess(), saveRes.getId(), saveRes.getErrors()); + } + batchRowCounter++; + if (results.length < batchRowCounter) { + getLogger().fatal(Messages.getString("Visitor.errorResultsLength")); //$NON-NLS-1$ + throw new LoadException(Messages.getString("Visitor.errorResultsLength")); + } + } + if (results.length > batchRowCounter) { + getLogger().fatal(Messages.getString("Visitor.errorResultsLength")); //$NON-NLS-1$ + throw new LoadException(Messages.getString("Visitor.errorResultsLength")); + } + } + + private void setHeaders() { + // TODO Auto-generated method stub + + } + + @Override + protected int getMaxBytesInBatch() { + return AppConfig.MAX_REST_API_IMPORT_BATCH_BYTES; + } + @Override + protected int getBytesInBean(DynaBean dynaBean) { + return dynaBean.toString().length(); + } + + /** + * This method performs the actual client action. It must be implemented by all subclasses. It returns an object[] + * because of saveResult[] and deleteResult[], while do the exact same thing, are two different classes without + * common inheritance. And we're stuck with it for legacy reasons. + * + * @throws ConnectionException + */ + protected abstract Object[] executeClientAction(CompositeRESTClient client, List data) + throws ConnectionException; +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTUpdateVisitor.java b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTUpdateVisitor.java new file mode 100644 index 000000000..788ab80e7 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/action/visitor/rest/RESTUpdateVisitor.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.action.visitor.rest; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.CompositeRESTClient; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataWriter; +import com.sforce.ws.ConnectionException; + +public class RESTUpdateVisitor extends RESTLoadVisitor { + + public RESTUpdateVisitor(Controller controller, ILoaderProgress monitor, DataWriter successWriter, + DataWriter errorWriter) { + super(controller, monitor, successWriter, errorWriter); + } + + protected Object[] executeClientAction(CompositeRESTClient client, List dynabeans) throws ConnectionException { + return client.loadAction(CompositeRESTClient.ACTION_ENUM.UPDATE, dynabeans); + } +} diff --git a/src/main/java/com/salesforce/dataloader/client/BulkClient.java b/src/main/java/com/salesforce/dataloader/client/BulkClient.java deleted file mode 100644 index 9e4081e93..000000000 --- a/src/main/java/com/salesforce/dataloader/client/BulkClient.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.client; - -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; -import com.sforce.async.AsyncApiException; -import com.sforce.async.BulkConnection; -import com.sforce.ws.ConnectorConfig; - -/** - * Wrapper for the async api client - * - * @author Colin Jarvis - * @since 17.0 - */ -public class BulkClient extends ClientBase { - private static Logger LOG = Logger.getLogger(BulkClient.class); - private BulkConnection client; - - public BulkClient(Controller controller) { - super(controller, LOG); - } - - @Override - public BulkConnection getClient() { - return client; - } - - @Override - protected boolean connectPostLogin(ConnectorConfig cc) { - try { - // Set up a connection object with the given config - this.client = new BulkConnection(cc); - - } catch (AsyncApiException e) { - logger.error(Messages.getMessage(getClass(), "loginError", cc.getAuthEndpoint(), e.getExceptionMessage()), - e); - // Wrap exception. Otherwise, we'll have to change lots of signatures - throw new RuntimeException(e.getExceptionMessage(), e); - } - return true; - } - - @Override - protected ConnectorConfig getConnectorConfig() { - ConnectorConfig cc = super.getConnectorConfig(); - cc.setTraceMessage(config.getBoolean(Config.WIRE_OUTPUT)); - return cc; - } - -} diff --git a/src/main/java/com/salesforce/dataloader/client/BulkV1Client.java b/src/main/java/com/salesforce/dataloader/client/BulkV1Client.java new file mode 100644 index 000000000..93d0de763 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/BulkV1Client.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.client; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import com.salesforce.dataloader.action.visitor.bulk.BulkV1Connection; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.sforce.async.AsyncApiException; +import com.sforce.ws.ConnectorConfig; + +/** + * Wrapper for the async api client + * + * @author Colin Jarvis + * @since 17.0 + */ +public class BulkV1Client extends RESTClient { + private static Logger LOG = DLLogManager.getLogger(BulkV1Client.class); + + public BulkV1Client(Controller controller) { + super(controller, LOG); + } + + @Override + protected boolean connectPostLogin(ConnectorConfig cc) { + try { + // Set up a connection object with the given config + setConnection(new BulkV1Connection(cc)); + } catch (AsyncApiException e) { + logger.error(Messages.getMessage(getClass(), "loginError", cc.getAuthEndpoint(), e.getExceptionMessage()), + e); + // Wrap exception. Otherwise, we'll have to change lots of signatures + throw new RuntimeException(e.getExceptionMessage(), e); + } + return true; + } + + public static String getServicePath() { + return "/services/async/" + getAPIVersionForTheSession() + "/"; + } + + @Override + public String getServiceURLPath() { + return getServicePath(); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/client/BulkV2Client.java b/src/main/java/com/salesforce/dataloader/client/BulkV2Client.java new file mode 100644 index 000000000..8213db7ad --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/BulkV2Client.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.client; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.action.visitor.bulk.BulkV2Connection; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.sforce.async.AsyncApiException; +import com.sforce.ws.ConnectorConfig; + +public class BulkV2Client extends RESTClient { + private static Logger LOG = DLLogManager.getLogger(BulkV2Client.class); + + public BulkV2Client(Controller controller) { + super(controller, LOG); + } + + @Override + protected boolean connectPostLogin(ConnectorConfig cc) { + try { + // Set up a connection object with the given config + setConnection(new BulkV2Connection(cc, controller)); + } catch (AsyncApiException e) { + logger.error(Messages.getMessage(getClass(), "loginError", cc.getAuthEndpoint(), e.getExceptionMessage()), + e); + // Wrap exception. Otherwise, we'll have to change lots of signatures + throw new RuntimeException(e.getExceptionMessage(), e); + } + return true; + } + + public static String getServicePath() { + return "/services/data/v" + getAPIVersionForTheSession() + "/jobs/"; + } + + @Override + public String getServiceURLPath() { + return getServicePath(); + } +} diff --git a/src/main/java/com/salesforce/dataloader/client/ClientBase.java b/src/main/java/com/salesforce/dataloader/client/ClientBase.java index cb442d9e5..ee2c6427c 100644 --- a/src/main/java/com/salesforce/dataloader/client/ClientBase.java +++ b/src/main/java/com/salesforce/dataloader/client/ClientBase.java @@ -26,17 +26,17 @@ package com.salesforce.dataloader.client; import java.io.FileNotFoundException; -import java.net.MalformedURLException; -import java.net.URL; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; import com.salesforce.dataloader.client.SessionInfo.NotLoggedInException; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.soap.partner.Connector; +import com.sforce.soap.partner.GetUserInfoResult; import com.sforce.ws.ConnectorConfig; /** @@ -45,38 +45,35 @@ * @author Colin Jarvis * @since 17.0 */ -public abstract class ClientBase { +public abstract class ClientBase { - private static Logger LOG = Logger.getLogger(PartnerClient.class); - - protected static final URL DEFAULT_AUTH_ENDPOINT_URL; - static { - URL loginUrl; - try { - loginUrl = new URL(Connector.END_POINT); - } catch (MalformedURLException ex) { - LOG.error(ex); - throw new RuntimeException(ex); - } - DEFAULT_AUTH_ENDPOINT_URL = loginUrl; - } - - public static final String REST_ENDPOINT = "/services/async/" + Controller.API_VERSION; + private static String apiVersionForTheSession = getCurrentAPIVersionInWSC(); protected final Logger logger; protected final Controller controller; - protected final Config config; + protected final AppConfig appConfig; + private ConnectionType connectionType; private SessionInfo session = new SessionInfo(); protected abstract boolean connectPostLogin(ConnectorConfig connectorConfig); - public abstract ClientType getClient(); - + public ConnectionType getConnection() { + return this.connectionType; + } + + protected void setConnection(ConnectionType connType) { + this.connectionType = connType; + } + protected ClientBase(Controller controller, Logger logger) { this.controller = controller; - this.config = controller.getConfig(); + this.appConfig = controller.getAppConfig(); this.logger = logger; + String apiVersionStr = appConfig.getString(AppConfig.PROP_API_VERSION); + if (apiVersionStr != null && !apiVersionStr.isEmpty()) { + apiVersionForTheSession = apiVersionStr; + } } public final boolean connect(SessionInfo sess) { @@ -86,76 +83,61 @@ public final boolean connect(SessionInfo sess) { private static final String BASE_CLIENT_NAME = "DataLoader"; private static final String BULK_API_CLIENT_TYPE = "Bulk"; + private static final String BULK_V2_API_CLIENT_TYPE = "Bulkv2"; private static final String PARTNER_API_CLIENT_TYPE = "Partner"; private static final String BATCH_CLIENT_STRING = "Batch"; private static final String UI_CLIENT_STRING = "UI"; + public static final String SFORCE_CALL_OPTIONS_HEADER = "Sforce-Call-Options"; - protected static String getClientName(Config cfg) { - final String apiType = cfg.isBulkAPIEnabled() ? BULK_API_CLIENT_TYPE : PARTNER_API_CLIENT_TYPE; + public static String getClientName(AppConfig cfg) { + String apiType = PARTNER_API_CLIENT_TYPE; final String interfaceType = cfg.isBatchMode() ? BATCH_CLIENT_STRING : UI_CLIENT_STRING; + if (cfg.isBulkAPIEnabled()) { + apiType = BULK_API_CLIENT_TYPE; + }else if (cfg.isBulkV2APIEnabled()) { + apiType = BULK_V2_API_CLIENT_TYPE; + } return new StringBuilder(32).append(BASE_CLIENT_NAME).append(apiType).append(interfaceType) - .append("/").append(Controller.API_VERSION).toString(); //$NON-NLS-1$ + .append("/") + .append(Controller.APP_VERSION) + .toString(); //$NON-NLS-1$ + } + + public static synchronized String getAPIVersionForTheSession() { + return apiVersionForTheSession; + } + + public static synchronized void setAPIVersionForTheSession(String version) { + apiVersionForTheSession = version; } - protected ConnectorConfig getConnectorConfig() { + public ConnectorConfig getConnectorConfig() { ConnectorConfig cc = new ConnectorConfig(); - cc.setTransport(HttpClientTransport.class); + cc.setTransportFactory(new TransportFactoryImpl()); cc.setSessionId(getSessionId()); - + cc.setRequestHeader(SFORCE_CALL_OPTIONS_HEADER, + "client=" + ClientBase.getClientName(this.appConfig)); // set authentication credentials // blank username is not acceptible - String username = config.getString(Config.USERNAME); - boolean isManualSession = config.getBoolean(Config.SFDC_INTERNAL) && config.getBoolean(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN); - boolean isOAuthSession = config.getString(Config.OAUTH_ACCESSTOKEN) != null && config.getString(Config.OAUTH_ACCESSTOKEN).trim().length() > 0; + String username = appConfig.getString(AppConfig.PROP_USERNAME); + boolean isManualSession = appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL) && appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN); + boolean isOAuthSession = appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN) != null && appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN).trim().length() > 0; if (!isManualSession && !isOAuthSession && (username == null || username.length() == 0)) { - String errMsg = Messages.getMessage(getClass(), "emptyUsername", Config.USERNAME); + String errMsg = Messages.getMessage(getClass(), "emptyUsername", AppConfig.PROP_USERNAME); logger.error(errMsg); throw new IllegalStateException(errMsg); } cc.setUsername(username); - cc.setPassword(config.getString(Config.PASSWORD)); - - // proxy properties - try { - String proxyHost = config.getString(Config.PROXY_HOST); - int proxyPort = config.getInt(Config.PROXY_PORT); - if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { - logger.info(Messages.getFormattedString( - "Client.sforceLoginProxyDetail", new String[] { proxyHost, String.valueOf(proxyPort) })); //$NON-NLS-1$ - cc.setProxy(proxyHost, proxyPort); - - String proxyUsername = config.getString(Config.PROXY_USERNAME); - if (proxyUsername != null && proxyUsername.length() > 0) { - logger.info(Messages.getFormattedString("Client.sforceLoginProxyUser", proxyUsername)); //$NON-NLS-1$ - cc.setProxyUsername(proxyUsername); - - String proxyPassword = config.getString(Config.PROXY_PASSWORD); - if (proxyPassword != null && proxyPassword.length() > 0) { - logger.info(Messages.getString("Client.sforceLoginProxyPassword")); //$NON-NLS-1$ - cc.setProxyPassword(proxyPassword); - } else { - cc.setProxyPassword(""); - } - } - - String proxyNtlmDomain = config.getString(Config.PROXY_NTLM_DOMAIN); - if (proxyNtlmDomain != null && proxyNtlmDomain.length() > 0) { - logger.info(Messages.getFormattedString("Client.sforceLoginProxyNtlm", proxyNtlmDomain)); //$NON-NLS-1$ - cc.setNtlmDomain(proxyNtlmDomain); - } - } - - } catch (ParameterLoadException e) { - logger.error(e.getMessage()); - } + cc.setPassword(appConfig.getString(AppConfig.PROP_PASSWORD)); + AppUtil.setConnectorConfigProxySettings(appConfig, cc); // Time out after 5 seconds for connection int connTimeoutSecs; try { - connTimeoutSecs = config.getInt(Config.CONNECTION_TIMEOUT_SECS); + connTimeoutSecs = appConfig.getInt(AppConfig.PROP_CONNECTION_TIMEOUT_SECS); } catch (ParameterLoadException e1) { - connTimeoutSecs = Config.DEFAULT_CONNECTION_TIMEOUT_SECS; + connTimeoutSecs = AppConfig.DEFAULT_CONNECTION_TIMEOUT_SECS; } cc.setConnectionTimeout(connTimeoutSecs * 1000); @@ -163,21 +145,21 @@ protected ConnectorConfig getConnectorConfig() { // set timeout for operations based on config int timeoutSecs; try { - timeoutSecs = config.getInt(Config.TIMEOUT_SECS); + timeoutSecs = appConfig.getInt(AppConfig.PROP_TIMEOUT_SECS); } catch (ParameterLoadException e) { - timeoutSecs = Config.DEFAULT_TIMEOUT_SECS; + timeoutSecs = AppConfig.DEFAULT_TIMEOUT_SECS; } cc.setReadTimeout((timeoutSecs * 1000)); // use compression or turn it off - if (config.contains(Config.NO_COMPRESSION)) { - cc.setCompression(!config.getBoolean(Config.NO_COMPRESSION)); + if (appConfig.contains(AppConfig.PROP_NO_COMPRESSION)) { + cc.setCompression(!appConfig.getBoolean(AppConfig.PROP_NO_COMPRESSION)); } - if (config.getBoolean(Config.DEBUG_MESSAGES)) { + if (appConfig.getBoolean(AppConfig.PROP_DEBUG_MESSAGES)) { cc.setTraceMessage(true); cc.setPrettyPrintXml(true); - String filename = config.getString(Config.DEBUG_MESSAGES_FILE); + String filename = appConfig.getString(AppConfig.PROP_DEBUG_MESSAGES_FILE); if (filename.length() > 0) { try { cc.setTraceFile(filename); @@ -186,16 +168,29 @@ protected ConnectorConfig getConnectorConfig() { } } } - String server = getSession().getServer(); if (server != null) { - cc.setAuthEndpoint(server + DEFAULT_AUTH_ENDPOINT_URL.getPath()); - cc.setServiceEndpoint(server + DEFAULT_AUTH_ENDPOINT_URL.getPath()); - cc.setRestEndpoint(server + REST_ENDPOINT); + cc.setAuthEndpoint(server + PartnerClient.getServicePath()); + cc.setServiceEndpoint(server + PartnerClient.getServicePath()); // Partner SOAP service + cc.setRestEndpoint(server + BulkV1Client.getServicePath()); // REST service: Bulk v1 } + cc.setTraceMessage(appConfig.getBoolean(AppConfig.PROP_WIRE_OUTPUT)); return cc; } + + public static String getCurrentAPIVersionInWSC() { + String[] connectURLArray = Connector.END_POINT.split("\\/"); + return connectURLArray[connectURLArray.length-1]; + } + + public static String getPreviousAPIVersionInWSC() { + String currentAPIVersion = getCurrentAPIVersionInWSC(); + String[] versionStrArray = currentAPIVersion.split("\\."); + String currentMajorVerStr = versionStrArray[0]; + int currentMajorVer = Integer.parseInt(currentMajorVerStr); + return Integer.toString(currentMajorVer-1) + ".0"; + } public SessionInfo getSession() { return this.session; @@ -205,9 +200,9 @@ protected void clearSession() { setSession(new SessionInfo()); } - protected void setSession(String sessionId, String server) { + protected void setSession(String sessionId, String server, GetUserInfoResult userInfo) { - setSession(new SessionInfo(sessionId, server)); + setSession(new SessionInfo(sessionId, server, userInfo)); } private void setSession(SessionInfo sess) { diff --git a/src/main/java/com/salesforce/dataloader/client/CompositeRESTClient.java b/src/main/java/com/salesforce/dataloader/client/CompositeRESTClient.java new file mode 100644 index 000000000..20ed9ee26 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/CompositeRESTClient.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.client; + +import java.util.List; + +import org.apache.commons.beanutils.DynaBean; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.action.visitor.rest.RESTConnection; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.sforce.async.AsyncApiException; +import com.sforce.soap.partner.SaveResult; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; + +public class CompositeRESTClient extends RESTClient { + public static enum ACTION_ENUM {UPDATE, DELETE}; + private static Logger LOG = DLLogManager.getLogger(BulkV2Client.class); + + public CompositeRESTClient(Controller controller) { + super(controller, LOG); + } + + @Override + protected boolean connectPostLogin(ConnectorConfig cc) { + try { + // Set up a connection object with the given config + setConnection(new RESTConnection(cc, controller)); + } catch (AsyncApiException e) { + logger.error(Messages.getMessage(getClass(), "loginError", cc.getAuthEndpoint(), e.getExceptionMessage()), + e); + // Wrap exception. Otherwise, we'll have to change lots of signatures + throw new RuntimeException(e.getExceptionMessage(), e); + } + return true; + } + + public static String getServicePath() { + return "/services/data/v" + getAPIVersionForTheSession() + "/composite/sobjects/"; + } + + public SaveResult[] loadAction(ACTION_ENUM action, List dynabeans) throws ConnectionException { + return getConnection().loadAction(getSession(), action, dynabeans); + } + + @Override + public String getServiceURLPath() { + return getServicePath(); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/client/DefaultSimplePost.java b/src/main/java/com/salesforce/dataloader/client/DefaultSimplePost.java index e3fb46b8b..965f01bba 100644 --- a/src/main/java/com/salesforce/dataloader/client/DefaultSimplePost.java +++ b/src/main/java/com/salesforce/dataloader/client/DefaultSimplePost.java @@ -26,30 +26,15 @@ package com.salesforce.dataloader.client; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.exception.ParameterLoadException; -import com.sforce.ws.tools.VersionInfo; - -import org.apache.commons.io.IOUtils; -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.NTCredentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.entity.UrlEncodedFormEntity; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.ws.ConnectorConfig; +import org.apache.http.Header; import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.impl.client.*; import org.apache.http.message.BasicNameValuePair; import java.io.*; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.util.Arrays; -import java.util.zip.GZIPInputStream; /** * simplied http client transport for posts (used for oauth) @@ -59,84 +44,37 @@ public class DefaultSimplePost implements SimplePost { public static final int PROXY_AUTHENTICATION_REQUIRED = 407; private boolean successful; - private Config config; + private AppConfig appConfig; private String endpoint; private BasicNameValuePair[] pairs; private InputStream input; private int statusCode; private String reasonPhrase; + private CloseableHttpResponse response; - DefaultSimplePost(Config config, String endpoint, BasicNameValuePair... pairs) { - this.config = config; + DefaultSimplePost(AppConfig appConfig, String endpoint, BasicNameValuePair... pairs) { + this.appConfig = appConfig; this.endpoint = endpoint; this.pairs = pairs; } + + public void addBasicNameValuePair(BasicNameValuePair pair) { + BasicNameValuePair[] newPairs = new BasicNameValuePair[pairs.length + 1]; + for (int i=0; i < pairs.length; i++) { + newPairs[i] = pairs[i]; + } + newPairs[pairs.length] = pair; + pairs = newPairs; + } @Override public void post() throws IOException, ParameterLoadException { - HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - HttpPost post = new HttpPost(endpoint); - UrlEncodedFormEntity entity = new UrlEncodedFormEntity(Arrays.asList(pairs)); - - int proxyPort = config.getInt(Config.PROXY_PORT); - String proxyHostName = config.getString(Config.PROXY_HOST); - String proxyUser = config.getString(Config.PROXY_USERNAME); - String proxyPassword = config.getString(Config.PROXY_PASSWORD); - String ntlmDomain = config.getString(Config.PROXY_NTLM_DOMAIN); - proxyHostName = proxyHostName != null ? proxyHostName.trim() : ""; - proxyUser = proxyUser != null ? proxyUser.trim() : ""; - proxyPassword = proxyPassword != null ? proxyPassword.trim() : ""; - ntlmDomain = ntlmDomain != null ? ntlmDomain.trim() : ""; - - post.addHeader("User-Agent", VersionInfo.info()); - post.setEntity(entity); - - //proxy - if (proxyHostName.length() > 0) { - InetSocketAddress proxyAddress = new InetSocketAddress(proxyHostName, proxyPort); - HttpHost proxyHost = new HttpHost(proxyAddress.getHostName(), proxyAddress.getPort(), "http"); - AuthScope scope = new AuthScope(proxyAddress.getHostName(), proxyAddress.getPort(), null, null); - Credentials credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword); - - if (ntlmDomain.length() > 0) { - credentials = new NTCredentials(proxyUser, proxyPassword, InetAddress.getLocalHost().getCanonicalHostName(), ntlmDomain); - } - - RequestConfig.Builder requestConfigBuilder = RequestConfig.custom(); - requestConfigBuilder.setProxy(proxyHost); - post.setConfig(requestConfigBuilder.build()); - - CredentialsProvider credentialsprovider = new BasicCredentialsProvider(); - credentialsprovider.setCredentials(scope, credentials); - httpClientBuilder.setDefaultCredentialsProvider(credentialsprovider).build(); - } - - try (CloseableHttpClient httpClient = httpClientBuilder.build()) { - - if (ntlmDomain.length() > 0) { - // need to send a HEAD request to trigger NTLM authentication - HttpHead head = new HttpHead("http://salesforce.com"); - try (CloseableHttpResponse ignored = httpClient.execute(head)) { - } - } - try (CloseableHttpResponse response = httpClient.execute(post)) { - - successful = response.getStatusLine().getStatusCode() < 400; - statusCode = response.getStatusLine().getStatusCode(); - reasonPhrase = response.getStatusLine().getReasonPhrase(); - - // copy input stream data into a new input stream because releasing the connection will close the input stream - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); - try (InputStream inStream = response.getEntity().getContent()) { - IOUtils.copy(inStream, bOut); - input = new ByteArrayInputStream(bOut.toByteArray()); - if (response.containsHeader("Content-Encoding") && response.getHeaders("Content-Encoding")[0].getValue().equals("gzip")) { - input = new GZIPInputStream(input); - } - } - - } - } + ConnectorConfig connConfig = new ConnectorConfig(); + AppUtil.setConnectorConfigProxySettings(appConfig, connConfig); + HttpClientTransport clientTransport = HttpClientTransport.getInstance(); + clientTransport.setConfig(connConfig); + this.input = clientTransport.simplePost(endpoint, null, pairs); + successful = clientTransport.isSuccessful(); } @Override @@ -158,4 +96,8 @@ public int getStatusCode() { public String getReasonPhrase() { return reasonPhrase; } + + public Header[] getResponseHeaders(String headerName) { + return response.getHeaders(headerName); + } } diff --git a/src/main/java/com/salesforce/dataloader/client/DescribeRefObject.java b/src/main/java/com/salesforce/dataloader/client/DescribeRefObject.java index 7464cb7a2..d6ec97a52 100644 --- a/src/main/java/com/salesforce/dataloader/client/DescribeRefObject.java +++ b/src/main/java/com/salesforce/dataloader/client/DescribeRefObject.java @@ -38,19 +38,26 @@ */ public class DescribeRefObject { - private String objectName; - private Map fieldInfoMap; + private String parentObjectName; + private Map parentFieldInfoMap; + private Field childField; + public static final int MAX_PARENT_OBJECTS_IN_REFERENCING_FIELD = 5; - DescribeRefObject(String objectName, Map fieldInfoMap) { - this.objectName = objectName; - this.fieldInfoMap = fieldInfoMap; + DescribeRefObject(String parentObjectName, Field childField, Map fieldInfoMap) { + this.parentObjectName = parentObjectName; + this.parentFieldInfoMap = fieldInfoMap; + this.childField = childField; } - public Map getFieldInfoMap() { - return fieldInfoMap; + public Map getParentObjectFieldMap() { + return parentFieldInfoMap; } - public String getObjectName() { - return objectName; + public String getParentObjectName() { + return parentObjectName; + } + + public Field getChildField() { + return childField; } } diff --git a/src/main/java/com/salesforce/dataloader/client/HttpClientTransport.java b/src/main/java/com/salesforce/dataloader/client/HttpClientTransport.java index 3c9b6e71c..60cbe854f 100644 --- a/src/main/java/com/salesforce/dataloader/client/HttpClientTransport.java +++ b/src/main/java/com/salesforce/dataloader/client/HttpClientTransport.java @@ -27,7 +27,10 @@ import java.io.*; import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.HashMap; +import java.util.Map; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; @@ -35,18 +38,39 @@ import org.apache.http.*; import org.apache.http.auth.*; import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpPatch; import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.exception.HttpClientTransportException; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.async.AsyncApiException; import com.sforce.ws.ConnectorConfig; + import com.sforce.ws.tools.VersionInfo; import com.sforce.ws.transport.*; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.ProxyAuthenticationStrategy; +import org.apache.http.message.BasicNameValuePair; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; /** * This class implements the Transport interface for WSC with HttpClient in order to properly work @@ -56,91 +80,168 @@ * @author Jeff Lai * @since 25.0.2 */ -public class HttpClientTransport implements Transport { +public class HttpClientTransport implements HttpTransportInterface { + + + private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer "; + private static final String AUTH_HEADER_FOR_JSON = "Authorization"; + private static final String AUTH_HEADER_FOR_XML = "X-SFDC-Session"; + private static final String USER_AGENT_HEADER = "User-Agent"; - private ConnectorConfig config; + private static ConnectorConfig currentConnectorConfig = null; private boolean successful; - private HttpPost post; + private HttpRequestBase httpMethod = null; private OutputStream output; private ByteArrayOutputStream entityByteOut; - - public HttpClientTransport() { - } - - public HttpClientTransport(ConnectorConfig config) { - setConfig(config); - } + private static CloseableHttpClient currentHttpClient = null; + private static long serverInvocationCount = 0; + private static Logger logger = DLLogManager.getLogger(HttpClientTransport.class); + private HttpResponse httpResponse; + private static final HttpClientTransport singletonTransportInstance = new HttpClientTransport(); @Override - public void setConfig(ConnectorConfig config) { - this.config = config; + public synchronized void setConfig(ConnectorConfig newConfig) { + if (!canReuseHttpClient(currentConnectorConfig, newConfig) + && currentHttpClient != null) { + closeHttpClient(); + } + currentConnectorConfig = newConfig; + if (currentConnectorConfig != null && currentHttpClient == null) { + try { + initializeHttpClient(); + } catch (UnknownHostException e) { + logger.error("Unable to initialize HttpClient " + e.getMessage()); + } + } } - @Override - public OutputStream connect(String url, String soapAction) throws IOException { - if (soapAction == null) { - soapAction = ""; + private boolean canReuseHttpClient(ConnectorConfig config1, ConnectorConfig config2) { + if (!isReuseHttpClient()) { + return false; } - - HashMap header = new HashMap(); - - header.put("SOAPAction", "\"" + soapAction + "\""); - header.put("Content-Type", "text/xml; charset=UTF-8"); - header.put("Accept", "text/xml"); - - return connect(url, header); + if (config1 == config2) { + return true; + } else if (config1 == null || config2 == null) { + // one of the configs is null, other isn't. They can't be equal. + return false; + } + + InetSocketAddress proxy1Address = (InetSocketAddress)config1.getProxy().address(); + InetSocketAddress proxy2Address = (InetSocketAddress)config2.getProxy().address(); + + if ((proxy1Address == null && proxy2Address != null) + || (proxy1Address != null && proxy2Address == null)) { + return false; + } else if (proxy1Address != null && proxy2Address != null) { + String field1, field2; + field1 = config1.getProxyUsername() == null ? "" : config1.getProxyUsername(); + field2 = config2.getProxyUsername() == null ? "" : config2.getProxyUsername(); + if (field1.compareTo(field2) != 0) { + return false; + } + + field1 = config1.getProxyPassword() == null ? "" : config1.getProxyPassword(); + field2 = config2.getProxyPassword() == null ? "" : config2.getProxyPassword(); + if (field1.compareTo(field2) != 0) { + return false; + } + + field1 = config1.getNtlmDomain() == null ? "" : config1.getNtlmDomain(); + field2 = config2.getNtlmDomain() == null ? "" : config2.getNtlmDomain(); + if (field1.compareTo(field2) != 0) { + return false; + } + + field1 = proxy1Address.getHostName() == null ? "" : proxy1Address.getHostName(); + field2 = proxy2Address.getHostName() == null ? "" : proxy2Address.getHostName(); + if (field1.compareTo(field2) != 0) { + return false; + } + + int intField1 = proxy1Address.getPort(); + int intField2 = proxy2Address.getPort(); + if (intField1 != intField2) { + return false; + } + } + return true; } - - @Override - public InputStream getContent() throws IOException { - InputStream input; + + private synchronized void initializeHttpClient() throws UnknownHostException { + closeHttpClient(); + httpMethod = null; HttpClientBuilder httpClientBuilder = HttpClientBuilder.create(); - if (config.getProxy().address() != null) { - String proxyUser = config.getProxyUsername() == null ? "" : config.getProxyUsername(); - String proxyPassword = config.getProxyPassword() == null ? "" : config.getProxyPassword(); + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_USE_SYSTEM_PROPS_FOR_HTTP_CLIENT)) { + httpClientBuilder = httpClientBuilder.useSystemProperties(); + } + + if (currentConnectorConfig != null + && currentConnectorConfig.getProxy() != null + && currentConnectorConfig.getProxy().address() != null) { + String proxyUser = currentConnectorConfig.getProxyUsername() == null ? "" : currentConnectorConfig.getProxyUsername(); + String proxyPassword = currentConnectorConfig.getProxyPassword() == null ? "" : currentConnectorConfig.getProxyPassword(); + + InetSocketAddress proxyAddress = (InetSocketAddress) currentConnectorConfig.getProxy().address(); + HttpHost proxyHost = new HttpHost(proxyAddress.getHostName(), proxyAddress.getPort(), "http"); + httpClientBuilder.setProxy(proxyHost); - Credentials credentials; + CredentialsProvider credentialsprovider = new BasicCredentialsProvider(); + AuthScope scope = new AuthScope(proxyAddress.getHostName(), proxyAddress.getPort(), null, null); - if (config.getNtlmDomain() != null && !config.getNtlmDomain().equals("")) { + Credentials credentials; + if (AppUtil.getOSType() == AppUtil.OSType.WINDOWS) { String computerName = InetAddress.getLocalHost().getCanonicalHostName(); - credentials = new NTCredentials(proxyUser, proxyPassword, computerName, config.getNtlmDomain()); + credentials = new NTCredentials(proxyUser, proxyPassword, computerName, currentConnectorConfig.getNtlmDomain()); } else { credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword); } - - InetSocketAddress proxyAddress = (InetSocketAddress) config.getProxy().address(); - HttpHost proxyHost = new HttpHost(proxyAddress.getHostName(), proxyAddress.getPort(), "http"); - httpClientBuilder.setProxy(proxyHost); - - CredentialsProvider credentialsprovider = new BasicCredentialsProvider(); - AuthScope scope = new AuthScope(proxyAddress.getHostName(), proxyAddress.getPort(), null, null); + // based on answer at https://stackoverflow.com/questions/6962047/apache-httpclient-4-1-proxy-authentication credentialsprovider.setCredentials(scope, credentials); httpClientBuilder.setDefaultCredentialsProvider(credentialsprovider); + httpClientBuilder.setProxyAuthenticationStrategy(new ProxyAuthenticationStrategy()); + currentHttpClient = httpClientBuilder.build(); } + if (currentHttpClient == null) { + currentHttpClient = httpClientBuilder.build(); + } + } - try (CloseableHttpClient httpClient = httpClientBuilder.build()) { - + @Override + public synchronized InputStream getContent() throws IOException { + serverInvocationCount++; + if (this.httpMethod instanceof HttpEntityEnclosingRequestBase + && ((HttpEntityEnclosingRequestBase)this.httpMethod).getEntity() == null) { byte[] entityBytes = entityByteOut.toByteArray(); HttpEntity entity = new ByteArrayEntity(entityBytes); - post.setEntity(entity); - - if (config.getNtlmDomain() != null && !config.getNtlmDomain().equals("")) { - // need to send a HEAD request to trigger NTLM authentication - try (CloseableHttpResponse ignored = httpClient.execute(new HttpHead("http://salesforce.com"))) { - } + currentConnectorConfig.setUseChunkedPost(false); + ((HttpEntityEnclosingRequestBase)this.httpMethod).setEntity(entity); + } + InputStream input = new ByteArrayInputStream(new byte[1]); + HttpClientContext context = HttpClientContext.create(); + RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(currentConnectorConfig.useChunkedPost()).build(); + context.setRequestConfig(config); + + if (currentConnectorConfig.getNtlmDomain() != null && !currentConnectorConfig.getNtlmDomain().equals("")) { + // need to send a HEAD request to trigger NTLM authentication + try (CloseableHttpResponse ignored = currentHttpClient.execute(new HttpHead("http://salesforce.com"))) { + } catch (Exception ex) { + logger.error(ex.getMessage()); + throw ex; } + } - try (CloseableHttpResponse response = httpClient.execute(post)) { - successful = true; - if (response.getStatusLine().getStatusCode() > 399) { - successful = false; - if (response.getStatusLine().getStatusCode() == 407) { - throw new RuntimeException(response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); - } + try (CloseableHttpResponse response = currentHttpClient.execute(this.httpMethod, context)) { + successful = true; + httpResponse = response; + if (response.getStatusLine().getStatusCode() > 399) { + successful = false; + if (response.getStatusLine().getStatusCode() == 407) { + throw new RuntimeException(response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); } - - // copy input stream data into a new input stream because releasing the connection will close the input stream - ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + } + // copy input stream data into a new input stream because releasing the connection will close the input stream + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + if (response.getEntity() != null) { try (InputStream inStream = response.getEntity().getContent()) { IOUtils.copy(inStream, bOut); input = new ByteArrayInputStream(bOut.toByteArray()); @@ -149,15 +250,35 @@ public InputStream getContent() throws IOException { } } } + bOut.close(); } return input; } + public HttpResponse getHttpResponse() { + return this.httpResponse; + } + @Override public boolean isSuccessful() { return successful; } + + @Override + public OutputStream connect(String url, String soapAction) throws IOException { + if (soapAction == null) { + soapAction = ""; + } + HashMap header = new HashMap(); + + header.put("SOAPAction", "\"" + soapAction + "\""); + header.put("Content-Type", "text/xml; charset=" + StandardCharsets.UTF_8.name()); + header.put("Accept", "text/xml"); + + return connect(url, header); + } + @Override public OutputStream connect(String endpoint, HashMap httpHeaders) throws IOException { return connect(endpoint, httpHeaders, true); @@ -165,40 +286,197 @@ public OutputStream connect(String endpoint, HashMap httpHeaders @Override public OutputStream connect(String endpoint, HashMap httpHeaders, boolean enableCompression) throws IOException { - post = new HttpPost(endpoint); - - for (String name : httpHeaders.keySet()) { - post.addHeader(name, httpHeaders.get(name)); - } + return connect(endpoint, httpHeaders, enableCompression, SupportedHttpMethodType.POST); + } + + @Override + public OutputStream connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + SupportedHttpMethodType httpMethod) throws IOException { + return doConnect(endpoint, httpHeaders, enableCompression, httpMethod, null, null); + } - post.addHeader("User-Agent", VersionInfo.info()); + @Override + public void connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + SupportedHttpMethodType httpMethod, InputStream contentInputStream, String contentEncoding) + throws IOException { + doConnect(endpoint, httpHeaders, enableCompression, httpMethod, contentInputStream, contentEncoding); + } - if (enableCompression) { - post.addHeader("Content-Encoding", "gzip"); - post.addHeader("Accept-Encoding", "gzip"); + public static long getServerInvocationCount() { + return serverInvocationCount; + } + + public static void resetServerInvocationCount() { + serverInvocationCount = 0; + } + + private OutputStream doConnect(String endpoint, + HashMap httpHeaders, + boolean enableCompression, + SupportedHttpMethodType httpMethodType, + InputStream requestInputStream, + String contentTypeStr) throws IOException { + configureHttpMethod(endpoint, httpHeaders, enableCompression, httpMethodType, + requestInputStream, contentTypeStr); + if (requestInputStream != null) { + // Request content is in an input stream. + // Caller won't be using an output stream to write request content to. + return null; } - entityByteOut = new ByteArrayOutputStream(); output = entityByteOut; - if (config.getMaxRequestSize() > 0) { - output = new LimitingOutputStream(config.getMaxRequestSize(), output); + if (currentConnectorConfig.getMaxRequestSize() > 0) { + output = new LimitingOutputStream(currentConnectorConfig.getMaxRequestSize(), output); } - if (enableCompression && config.isCompression()) { + if (enableCompression && currentConnectorConfig.isCompression()) { output = new GZIPOutputStream(output); } - if (config.isTraceMessage()) { - output = config.teeOutputStream(output); + if (currentConnectorConfig.isTraceMessage()) { + output = currentConnectorConfig.teeOutputStream(output); } - if (config.hasMessageHandlers()) { + if (currentConnectorConfig.hasMessageHandlers()) { URL url = new URL(endpoint); - output = new MessageHandlerOutputStream(config, url, output); + output = new MessageHandlerOutputStream(currentConnectorConfig, url, output); } return output; } + + private void configureHttpMethod( + String endpoint, + HashMap httpHeaders, + boolean enableCompression, + SupportedHttpMethodType httpMethodType, + InputStream requestInputStream, + String contentTypeStr + ) throws IOException { + switch (httpMethodType) { + case GET : + this.httpMethod = new HttpGet(endpoint); + break; + case PATCH : + this.httpMethod = new HttpPatch(endpoint); + break; + case PUT : + this.httpMethod = new HttpPut(endpoint); + break; + case DELETE : + this.httpMethod = new HttpDelete(endpoint); + break; + default: + this.httpMethod = new HttpPost(endpoint); + } + if (httpHeaders != null) { + for (String name : httpHeaders.keySet()) { + this.httpMethod.addHeader(name, httpHeaders.get(name)); + } + } + Map connectorHeaders = currentConnectorConfig.getHeaders(); + if (connectorHeaders != null) { + for (String name : connectorHeaders.keySet()) { + if (httpHeaders == null || !httpHeaders.containsKey(name)) { + this.httpMethod.addHeader(name, connectorHeaders.get(name)); + } + } + } + setAuthAndClientHeadersForHttpMethod(); + if (enableCompression && currentConnectorConfig.isCompression()) { + this.httpMethod.addHeader("Content-Encoding", "gzip"); + this.httpMethod.addHeader("Accept-Encoding", "gzip"); + } + if (requestInputStream != null) { + // caller has pre-specified input stream + ContentType contentType = ContentType.DEFAULT_TEXT; + if (contentTypeStr != null) { + contentType = ContentType.create(contentTypeStr); + } + BufferedHttpEntity entity = new BufferedHttpEntity(new InputStreamEntity(requestInputStream, contentType)); + currentConnectorConfig.setUseChunkedPost(true); + if (this.httpMethod instanceof HttpEntityEnclosingRequestBase) { + ((HttpEntityEnclosingRequestBase)this.httpMethod).setEntity(entity); + } + } + } + + public InputStream simplePost( + String endpoint, + HashMap httpHeaders, + BasicNameValuePair[] inputs) throws IOException { + configureHttpMethod(endpoint, httpHeaders, + false, SupportedHttpMethodType.POST, null, null); + UrlEncodedFormEntity entity = new UrlEncodedFormEntity(Arrays.asList(inputs)); + if (this.httpMethod instanceof HttpPost) { + ((HttpPost)this.httpMethod).setEntity(entity); + return this.getContent(); + } + return null; + } + + private void setAuthAndClientHeadersForHttpMethod() { + if (this.httpMethod != null + && currentConnectorConfig.getSessionId() != null + && !currentConnectorConfig.getSessionId().isBlank()) { + String authSessionId = currentConnectorConfig.getSessionId(); + Header authHeaderValForXML = this.httpMethod.getFirstHeader(AUTH_HEADER_FOR_XML); + Header authHeaderValForJSON = this.httpMethod.getFirstHeader(AUTH_HEADER_FOR_XML); + + if (authHeaderValForXML != null) { + authSessionId = authHeaderValForXML.getValue(); + } else if (authHeaderValForJSON != null) { + authSessionId = authHeaderValForJSON.getValue(); + } + if (authHeaderValForXML == null) { + this.httpMethod.addHeader(AUTH_HEADER_FOR_XML, AUTH_HEADER_VALUE_PREFIX + authSessionId); + } + if (authHeaderValForJSON == null) { + this.httpMethod.addHeader(AUTH_HEADER_FOR_JSON, AUTH_HEADER_VALUE_PREFIX + authSessionId); + } + } + Header userAgentHeaderVal = this.httpMethod.getFirstHeader(USER_AGENT_HEADER); + if (userAgentHeaderVal == null) { + this.httpMethod.addHeader(USER_AGENT_HEADER, VersionInfo.info()); + } + Header clientIdHeaderVal = this.httpMethod.getFirstHeader(AppConfig.CLIENT_ID_HEADER_NAME); + if (clientIdHeaderVal == null) { + AppConfig appConfig = AppConfig.getCurrentConfig(); + this.httpMethod.addHeader(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()); + } + Header clientNameHeaderVal = this.httpMethod.getFirstHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER); + if (clientNameHeaderVal == null) { + this.httpMethod.addHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER, + "client=" + ClientBase.getClientName(AppConfig.getCurrentConfig())); + + } + } + + public static void closeHttpClient() { + if (currentHttpClient != null) { + try { + currentHttpClient.close(); + } catch (IOException ex) { + // do nothing + } + currentHttpClient = null; + } + } -} + public static boolean isReuseHttpClient() { + AppConfig appConfig = AppConfig.getCurrentConfig(); + return appConfig.getBoolean(AppConfig.PROP_REUSE_CLIENT_CONNECTION); + } + + public InputStream httpGet(String urlStr) throws IOException, AsyncApiException, HttpClientTransportException { + InputStream in = null; + connect(urlStr, null, false, SupportedHttpMethodType.GET, null, null); + in = getContent(); + return in; + } + + public static HttpClientTransport getInstance() { + return singletonTransportInstance; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/client/HttpClientTransportv1.java b/src/main/java/com/salesforce/dataloader/client/HttpClientTransportv1.java new file mode 100644 index 000000000..d1e75df06 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/HttpClientTransportv1.java @@ -0,0 +1,477 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.client; + +import java.io.*; +import java.net.*; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; + +import org.apache.commons.io.IOUtils; +import org.apache.http.*; +import org.apache.http.auth.*; +import org.apache.http.client.CredentialsProvider; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpPatch; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.InputStreamEntity; + +import com.salesforce.dataloader.exception.HttpClientTransportException; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.async.AsyncApiException; +import com.sforce.ws.ConnectorConfig; +import com.sforce.ws.MessageHandler; +import com.sforce.ws.MessageHandlerWithHeaders; +import com.sforce.ws.tools.VersionInfo; +import com.sforce.ws.transport.*; +import com.sforce.ws.util.FileUtil; + +import org.apache.http.impl.client.BasicCredentialsProvider; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +/** + * This class implements the Transport interface for WSC with HttpClient in order to properly work + * with NTLM proxies. The existing JdkHttpTransport in WSC does not work with NTLM proxies when + * compiled on Java 1.6 + * + * @author Jeff Lai + * @since 25.0.2 + */ +public class HttpClientTransportv1 implements HttpTransportInterfacev1 { + + private static ConnectorConfig currentConfig = null; + private boolean successful; + private HttpRequestBase httpMethod; + private OutputStream output; + private ByteArrayOutputStream entityByteOut; + private static CloseableHttpClient currentHttpClient = null; + private static boolean reuseConnection = true; + private static long serverInvocationCount = 0; + private static Logger logger = DLLogManager.getLogger(HttpClientTransport.class); + + public HttpClientTransportv1() { + } + + public HttpClientTransportv1(ConnectorConfig newConfig) { + setConfig(newConfig); + } + + @Override + public synchronized void setConfig(ConnectorConfig newConfig) { + if (!areEquivalentConfigs(currentConfig, newConfig) && currentHttpClient != null) { + try { + currentHttpClient.close(); + } catch (IOException ex) { + // do nothing + } + currentHttpClient = null; + } + currentConfig = newConfig; + } + + @Override + public OutputStream connect(String url, String soapAction) throws IOException { + if (soapAction == null) { + soapAction = ""; + } + + HashMap header = new HashMap(); + + header.put("SOAPAction", "\"" + soapAction + "\""); + header.put("Content-Type", "text/xml; charset=" + StandardCharsets.UTF_8.name()); + header.put("Accept", "text/xml"); + + return connect(url, header); + } + + private boolean areEquivalentConfigs(ConnectorConfig config1, ConnectorConfig config2) { + if (config1 == null && config2 == null) { + return true; + } else if (config1 == null || config2 == null) { + // one of the configs is null, other isn't. They can't be equal. + return false; + } else if (config1.equals(config2)) { + return true; + } + + InetSocketAddress socketAddress1 = (InetSocketAddress)config1.getProxy().address(); + InetSocketAddress socketAddress2 = (InetSocketAddress)config2.getProxy().address(); + + if (socketAddress1 == null && socketAddress2 == null) { + return true; + } else if (socketAddress1 == null || socketAddress2 == null) { + return false; + } else { + String field1, field2; + field1 = config1.getProxyUsername() == null ? "" : config1.getProxyUsername(); + field2 = config2.getProxyUsername() == null ? "" : config2.getProxyUsername(); + if (field1.compareTo(field2) != 0) { + return false; + } + + field1 = config1.getProxyPassword() == null ? "" : config1.getProxyPassword(); + field2 = config2.getProxyPassword() == null ? "" : config2.getProxyPassword(); + if (field1.compareTo(field2) != 0) { + return false; + } + + field1 = config1.getNtlmDomain() == null ? "" : config1.getNtlmDomain(); + field2 = config2.getNtlmDomain() == null ? "" : config2.getNtlmDomain(); + if (field1.compareTo(field2) != 0) { + return false; + } + + field1 = socketAddress1.getHostName() == null ? "" : socketAddress1.getHostName(); + field2 = socketAddress2.getHostName() == null ? "" : socketAddress2.getHostName(); + if (field1.compareTo(field2) != 0) { + return false; + } + + int intField1 = socketAddress1.getPort(); + int intField2 = socketAddress2.getPort(); + if (intField1 != intField2) { + return false; + } + } + return true; + } + + private static synchronized void initializeHttpClient() throws UnknownHostException { + if (isReuseConnection() && currentHttpClient != null) { + // already initialized. + return; + } + closeConnections(); + HttpClientBuilder httpClientBuilder = HttpClientBuilder.create().useSystemProperties(); + + if (currentConfig.getProxy().address() != null) { + String proxyUser = currentConfig.getProxyUsername() == null ? "" : currentConfig.getProxyUsername(); + String proxyPassword = currentConfig.getProxyPassword() == null ? "" : currentConfig.getProxyPassword(); + + InetSocketAddress proxyAddress = (InetSocketAddress) currentConfig.getProxy().address(); + HttpHost proxyHost = new HttpHost(proxyAddress.getHostName(), proxyAddress.getPort(), "http"); + httpClientBuilder.setProxy(proxyHost); + + CredentialsProvider credentialsprovider = new BasicCredentialsProvider(); + AuthScope scope = new AuthScope(proxyAddress.getHostName(), proxyAddress.getPort(), null, null); + httpClientBuilder.setDefaultCredentialsProvider(credentialsprovider); + + Credentials credentials; + if (AppUtil.getOSType() == AppUtil.OSType.WINDOWS) { + String computerName = InetAddress.getLocalHost().getCanonicalHostName(); + credentials = new NTCredentials(proxyUser, proxyPassword, computerName, currentConfig.getNtlmDomain()); + } else { + credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword); + } + credentialsprovider.setCredentials(scope, credentials); + currentHttpClient = httpClientBuilder.build(); + if (AppUtil.getOSType() == AppUtil.OSType.WINDOWS) { + try (CloseableHttpResponse ignored = currentHttpClient.execute(new HttpHead("http://salesforce.com"))) { + } catch (Exception e) { + logger.info("Unable to use NTCredentials for proxy. Switching to UsernamePasswordCredentials"); + credentials = new UsernamePasswordCredentials(proxyUser, proxyPassword); + credentialsprovider.setCredentials(scope, credentials); + } + } + } + currentHttpClient = httpClientBuilder.build(); + } + + @Override + public synchronized InputStream getContent() throws IOException { + serverInvocationCount++; + initializeHttpClient(); + if (this.httpMethod instanceof HttpEntityEnclosingRequestBase + && ((HttpEntityEnclosingRequestBase)this.httpMethod).getEntity() == null) { + byte[] entityBytes = entityByteOut.toByteArray(); + HttpEntity entity = new ByteArrayEntity(entityBytes); + currentConfig.setUseChunkedPost(false); + ((HttpEntityEnclosingRequestBase)this.httpMethod).setEntity(entity); + } + InputStream input = new ByteArrayInputStream(new byte[1]); + try { + HttpClientContext context = HttpClientContext.create(); + RequestConfig config = RequestConfig.custom().setExpectContinueEnabled(currentConfig.useChunkedPost()).build(); + context.setRequestConfig(config); + + if (currentConfig.getNtlmDomain() != null && !currentConfig.getNtlmDomain().equals("")) { + // need to send a HEAD request to trigger NTLM authentication + try (CloseableHttpResponse ignored = currentHttpClient.execute(new HttpHead("http://salesforce.com"))) { + } catch (Exception ex) { + logger.error(ex.getMessage()); + throw ex; + } + } + + try (CloseableHttpResponse response = currentHttpClient.execute(this.httpMethod, context)) { + successful = true; + if (response.getStatusLine().getStatusCode() > 399) { + successful = false; + if (response.getStatusLine().getStatusCode() == 407) { + throw new RuntimeException(response.getStatusLine().getStatusCode() + " " + response.getStatusLine().getReasonPhrase()); + } + } + // copy input stream data into a new input stream because releasing the connection will close the input stream + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + if (response.getEntity() != null) { + try (InputStream inStream = response.getEntity().getContent()) { + IOUtils.copy(inStream, bOut); + input = new ByteArrayInputStream(bOut.toByteArray()); + if (response.containsHeader("Content-Encoding") && response.getHeaders("Content-Encoding")[0].getValue().equals("gzip")) { + input = new GZIPInputStream(input); + } + } + } + } + } finally { + entityByteOut.close(); + if (isReuseConnection()) { + closeConnections(); + } + } + return input; + } + + @Override + public boolean isSuccessful() { + return successful; + } + + @Override + public OutputStream connect(String endpoint, HashMap httpHeaders) throws IOException { + return connect(endpoint, httpHeaders, true); + } + + @Override + public OutputStream connect(String endpoint, HashMap httpHeaders, boolean enableCompression) throws IOException { + return connect(endpoint, httpHeaders, enableCompression, SupportedHttpMethodType.POST); + } + + @Override + public OutputStream connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + SupportedHttpMethodType httpMethod) throws IOException { + return doConnect(endpoint, httpHeaders, enableCompression, httpMethod, null, null); + } + + @Override + public void connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + SupportedHttpMethodType httpMethod, InputStream contentInputStream, String contentEncoding) + throws IOException { + doConnect(endpoint, httpHeaders, enableCompression, httpMethod, contentInputStream, contentEncoding); + } + + public static long getServerInvocationCount() { + return serverInvocationCount; + } + + public static void resetServerInvocationCount() { + serverInvocationCount = 0; + } + + private OutputStream doConnect(String endpoint, HashMap httpHeaders, boolean enableCompression, SupportedHttpMethodType httpMethodType, InputStream requestInputStream, String contentTypeStr) throws IOException { + switch (httpMethodType) { + case PATCH : + this.httpMethod = new HttpPatch(endpoint); + break; + case PUT : + this.httpMethod = new HttpPut(endpoint); + break; + case DELETE : + this.httpMethod = new HttpDelete(endpoint); + break; + default: + this.httpMethod = new HttpPost(endpoint); + } + for (String name : httpHeaders.keySet()) { + this.httpMethod.addHeader(name, httpHeaders.get(name)); + } + + this.httpMethod.addHeader("User-Agent", VersionInfo.info()); + + if (requestInputStream != null) { + ContentType contentType = ContentType.DEFAULT_TEXT; + if (contentTypeStr != null) { + contentType = ContentType.create(contentTypeStr); + } + BufferedHttpEntity entity = new BufferedHttpEntity(new InputStreamEntity(requestInputStream, contentType)); + currentConfig.setUseChunkedPost(true); + if (this.httpMethod instanceof HttpEntityEnclosingRequestBase) { + ((HttpEntityEnclosingRequestBase)this.httpMethod).setEntity(entity); + } + return null; + } + + if (enableCompression && currentConfig.isCompression()) { + this.httpMethod.addHeader("Content-Encoding", "gzip"); + this.httpMethod.addHeader("Accept-Encoding", "gzip"); + } + + entityByteOut = new ByteArrayOutputStream(); + output = entityByteOut; + + if (currentConfig.getMaxRequestSize() > 0) { + output = new LimitingOutputStream(currentConfig.getMaxRequestSize(), output); + } + + if (enableCompression && currentConfig.isCompression()) { + output = new GZIPOutputStream(output); + } + + if (currentConfig.isTraceMessage()) { + output = currentConfig.teeOutputStream(output); + } + + if (currentConfig.hasMessageHandlers()) { + URL url = new URL(endpoint); + output = new MessageHandlerOutputStream(currentConfig, url, output); + } + return output; + } + + public static void closeConnections() { + if (currentHttpClient != null) { + try { + currentHttpClient.close(); + } catch (IOException ex) { + // do nothing + } + currentHttpClient = null; + } + } + + public static void setReuseConnection(boolean reuse) { + reuseConnection = reuse; + } + + public static boolean isReuseConnection() { + return reuseConnection; + } + + private static final String AUTH_HEADER_VALUE_PREFIX = "Bearer "; + private static final String AUTH_HEADER = "Authorization"; + + public HttpURLConnection openHttpGetConnection(String urlStr, Map headers) throws IOException { + URL url = new URL(urlStr); + HttpURLConnection connection = currentConfig.createConnection(url, null); + SSLContext sslContext = currentConfig.getSslContext(); + if (sslContext != null && connection instanceof HttpsURLConnection) { + ((HttpsURLConnection)connection).setSSLSocketFactory(sslContext.getSocketFactory()); + } + if (headers != null && !headers.isEmpty()) { + Set headerNameSet = headers.keySet(); + for (String headerName : headerNameSet) { + connection.setRequestProperty(headerName, headers.get(headerName)); + } + } + String authHeaderValue = AUTH_HEADER_VALUE_PREFIX + currentConfig.getSessionId(); + connection.setRequestProperty(AUTH_HEADER, authHeaderValue); + return connection; + } + + public InputStream httpGet(HttpURLConnection connection, String urlStr) throws IOException, AsyncApiException, HttpClientTransportException { + boolean success = true; + InputStream in; + URL url = new URL(urlStr); + try { + in = connection.getInputStream(); + } catch (IOException e) { + success = false; + in = connection.getErrorStream(); + } + + String encoding = connection.getHeaderField("Content-Encoding"); + if ("gzip".equals(encoding)) { + in = new GZIPInputStream(in); + } + + if (currentConfig.isTraceMessage() || currentConfig.hasMessageHandlers()) { + byte[] bytes = FileUtil.toBytes(in); + in = new ByteArrayInputStream(bytes); + + if (currentConfig.hasMessageHandlers()) { + Iterator it = currentConfig.getMessagerHandlers(); + while (it.hasNext()) { + MessageHandler handler = it.next(); + if (handler instanceof MessageHandlerWithHeaders) { + ((MessageHandlerWithHeaders)handler).handleRequest(url, new byte[0], null); + ((MessageHandlerWithHeaders)handler).handleResponse(url, bytes, connection.getHeaderFields()); + } else { + handler.handleRequest(url, new byte[0]); + handler.handleResponse(url, bytes); + } + } + } + + if (currentConfig.isTraceMessage()) { + currentConfig.getTraceStream().println(url.toExternalForm()); + + Map> headers = connection.getHeaderFields(); + for (Map.Entry>entry : headers.entrySet()) { + StringBuffer sb = new StringBuffer(); + List values = entry.getValue(); + + if (values != null) { + for (String v : values) { + sb.append(v); + } + } + + currentConfig.getTraceStream().println(entry.getKey() + ": " + sb.toString()); + } + + currentConfig.teeInputStream(bytes); + } + } + + if (!success) { + HttpClientTransportException ex = new HttpClientTransportException("Unsuccessful GET operation", connection, in); + throw ex; + } + return in; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/client/HttpTransportInterface.java b/src/main/java/com/salesforce/dataloader/client/HttpTransportInterface.java new file mode 100644 index 000000000..8ed9eb78e --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/HttpTransportInterface.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.client; + +import java.io.OutputStream; +import java.io.InputStream; +import java.io.IOException; +import java.util.HashMap; + +import org.apache.http.HttpResponse; + +import com.salesforce.dataloader.exception.HttpClientTransportException; +import com.sforce.async.AsyncApiException; +import com.sforce.ws.transport.Transport; + +/** + * This interface defines a Transport. + * + * @author http://cheenath.com + * @version 1.0 + * @since 1.0 Nov 30, 2005 + */ +public interface HttpTransportInterface extends Transport { + enum SupportedHttpMethodType { + PUT, + POST, + PATCH, + DELETE, + GET + } + OutputStream connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + HttpTransportInterface.SupportedHttpMethodType httpMethod) throws IOException; + + void connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + HttpTransportInterface.SupportedHttpMethodType httpMethod, InputStream contentInputStream, String contentEncoding) throws IOException; + + InputStream httpGet(String urlStr) throws IOException, AsyncApiException, HttpClientTransportException; + HttpResponse getHttpResponse(); +} diff --git a/src/main/java/com/salesforce/dataloader/client/HttpTransportInterfacev1.java b/src/main/java/com/salesforce/dataloader/client/HttpTransportInterfacev1.java new file mode 100644 index 000000000..ce2c20ee3 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/HttpTransportInterfacev1.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.client; + +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.io.InputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import com.salesforce.dataloader.exception.HttpClientTransportException; +import com.sforce.async.AsyncApiException; +import com.sforce.ws.transport.Transport; + +/** + * This interface defines a Transport. + * + * @author http://cheenath.com + * @version 1.0 + * @since 1.0 Nov 30, 2005 + */ +public interface HttpTransportInterfacev1 extends Transport { + enum SupportedHttpMethodType { + PUT, + POST, + PATCH, + DELETE + } + OutputStream connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + HttpTransportInterfacev1.SupportedHttpMethodType httpMethod) throws IOException; + + void connect(String endpoint, HashMap httpHeaders, boolean enableCompression, + HttpTransportInterfacev1.SupportedHttpMethodType httpMethod, InputStream contentInputStream, String contentEncoding) throws IOException; + + HttpURLConnection openHttpGetConnection(String urlStr, Map headers) throws IOException; + InputStream httpGet(HttpURLConnection connection, String urlStr) throws IOException, AsyncApiException, HttpClientTransportException; +} diff --git a/src/main/java/com/salesforce/dataloader/client/PartnerClient.java b/src/main/java/com/salesforce/dataloader/client/PartnerClient.java index 254c28988..e389020f9 100644 --- a/src/main/java/com/salesforce/dataloader/client/PartnerClient.java +++ b/src/main/java/com/salesforce/dataloader/client/PartnerClient.java @@ -26,38 +26,70 @@ package com.salesforce.dataloader.client; +import com.salesforce.dataloader.action.OperationInfo; + /** * The sfdc api client class - implemented using the partner wsdl - * + * * @author Lexi Viripaeff * @since 6.0 */ -import java.lang.reflect.InvocationTargetException; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.*; - -import org.apache.commons.beanutils.DynaBean; -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dyna.ParentIdLookupFieldFormatter; import com.salesforce.dataloader.dyna.SforceDynaBean; import com.salesforce.dataloader.exception.ParameterLoadException; import com.salesforce.dataloader.exception.PasswordExpiredException; -import com.sforce.soap.partner.*; +import com.salesforce.dataloader.exception.RelationshipFormatException; +import com.salesforce.dataloader.mapping.LoadMapper; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.soap.partner.Connector; +import com.sforce.soap.partner.DeleteResult; +import com.sforce.soap.partner.DescribeGlobalResult; +import com.sforce.soap.partner.DescribeGlobalSObjectResult; +import com.sforce.soap.partner.DescribeSObjectResult; import com.sforce.soap.partner.Error; +import com.sforce.soap.partner.Field; +import com.sforce.soap.partner.GetUserInfoResult; +import com.sforce.soap.partner.LimitInfo; +import com.sforce.soap.partner.LimitInfoHeader_element; +import com.sforce.soap.partner.LoginResult; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.soap.partner.QueryResult; +import com.sforce.soap.partner.SaveResult; +import com.sforce.soap.partner.UndeleteResult; +import com.sforce.soap.partner.UpsertResult; import com.sforce.soap.partner.fault.ApiFault; +import com.sforce.soap.partner.fault.ExceptionCode; +import com.sforce.soap.partner.fault.LoginFault; +import com.sforce.soap.partner.fault.UnexpectedErrorFault; import com.sforce.soap.partner.sobject.SObject; -import com.sforce.ws.*; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; +import com.sforce.ws.SessionRenewer; + +import org.apache.commons.beanutils.DynaBean; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import java.lang.reflect.InvocationTargetException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; public class PartnerClient extends ClientBase { - private static Logger LOG = Logger.getLogger(PartnerClient.class); + private static Logger LOG = DLLogManager.getLogger(PartnerClient.class); - PartnerConnection client; + private ConnectorConfig connectorConfig = null; private static interface ClientOperation { String getName(); @@ -73,7 +105,7 @@ public String getName() { @Override public SaveResult[] run(SObject[] sObjects) throws ConnectionException { - return getClient().create(sObjects); + return getConnection().create(sObjects); } }; @@ -85,7 +117,7 @@ public String getName() { @Override public SaveResult[] run(SObject[] sObjects) throws ConnectionException { - return getClient().update(sObjects); + return getConnection().update(sObjects); } }; @@ -97,7 +129,7 @@ public String getName() { @Override public UpsertResult[] run(SObject[] sObjects) throws ConnectionException { - return getClient().upsert(config.getString(Config.EXTERNAL_ID_FIELD), sObjects); + return getConnection().upsert(appConfig.getString(AppConfig.PROP_IDLOOKUP_FIELD), sObjects); } }; @@ -109,10 +141,22 @@ public String getName() { @Override public DeleteResult[] run(String[] ids) throws ConnectionException { - return getClient().delete(ids); + return getConnection().delete(ids); } }; + private final ClientOperation UNDELETE_OPERATION = new ClientOperation() { + @Override + public String getName() { + return "undelete"; + } + + @Override + public UndeleteResult[] run(String[] ids) throws ConnectionException { + return getConnection().undelete(ids); + } + }; + private final ClientOperation QUERY_OPERATION = new ClientOperation() { @Override public String getName() { @@ -121,7 +165,8 @@ public String getName() { @Override public QueryResult run(String queryString) throws ConnectionException { - return getClient().query(queryString); + setExportBatchSize(); + return getConnection().query(queryString); } }; @@ -133,7 +178,8 @@ public String getName() { @Override public QueryResult run(String queryString) throws ConnectionException { - return getClient().queryAll(queryString); + setExportBatchSize(); + return getConnection().queryAll(queryString); } }; @@ -145,7 +191,8 @@ public String getName() { @Override public QueryResult run(String queryString) throws ConnectionException { - return getClient().queryMore(queryString); + setExportBatchSize(); + return getConnection().queryMore(queryString); } }; @@ -170,7 +217,7 @@ public String getName() { @Override public DescribeGlobalResult run(Object ignored) throws ConnectionException { - return getClient().describeGlobal(); + return getConnection().describeGlobal(); } }; @@ -182,67 +229,68 @@ public String getName() { @Override public DescribeSObjectResult run(String entity) throws ConnectionException { - return getClient().describeSObject(entity); + return getConnection().describeSObject(entity); } }; - private DescribeGlobalResult entityTypes; - private final Map referenceDescribes = new HashMap(); - private final Map describeGlobalResults = new HashMap(); - private final Map entityDescribes = new HashMap(); - - private final boolean enableRetries; - private final int maxRetries; - + private DescribeGlobalResult describeGlobalResults; + private ReferenceEntitiesDescribeMap referenceEntitiesDescribesMap = new ReferenceEntitiesDescribeMap(this); + private final Map describeGlobalResultsMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final Map entityFieldDescribesMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final Map parentDescribeCache = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); public PartnerClient(Controller controller) { super(controller, LOG); - int retries = -1; - this.enableRetries = config.getBoolean(Config.ENABLE_RETRIES); - if (this.enableRetries) { - try { - // limit the number of max retries in case limit is exceeded - retries = Math.min(Config.MAX_RETRIES_LIMIT, config.getInt(Config.MAX_RETRIES)); - } catch (ParameterLoadException e) { - retries = Config.DEFAULT_MAX_RETRIES; - } - } - this.maxRetries = retries; } public boolean connect() throws ConnectionException { return login(); } - @Override - protected boolean connectPostLogin(ConnectorConfig cc) { - if (getClient() == null) throw new IllegalStateException("Client should be logged in already"); - - getClient().setCallOptions(ClientBase.getClientName(this.config), null); + private void setExportBatchSize() { // query header - int querySize; + int querySize = AppConfig.DEFAULT_EXPORT_BATCH_SIZE; try { - querySize = config.getInt(Config.EXTRACT_REQUEST_SIZE); + querySize = appConfig.getInt(AppConfig.PROP_EXPORT_BATCH_SIZE); } catch (ParameterLoadException e) { - querySize = Config.DEFAULT_EXTRACT_REQUEST_SIZE; + querySize = AppConfig.DEFAULT_EXPORT_BATCH_SIZE; + } + if (querySize <= AppConfig.MIN_EXPORT_BATCH_SIZE) { + querySize = AppConfig.MIN_EXPORT_BATCH_SIZE; } - if (querySize > 0) { - getClient().setQueryOptions(querySize); + if (querySize > AppConfig.MAX_EXPORT_BATCH_SIZE) { + querySize = AppConfig.MAX_EXPORT_BATCH_SIZE; } + getConnection().setQueryOptions(querySize); + } + @Override + protected boolean connectPostLogin(ConnectorConfig cc) { + if (getConnection() == null) + throw new IllegalStateException("Client should be logged in already"); + getConnection().setCallOptions(ClientBase.getClientName(this.appConfig), null); + setExportBatchSize(); + // assignment rule for update - if (config.getString(Config.ASSIGNMENT_RULE).length() > 14) { - String rule = config.getString(Config.ASSIGNMENT_RULE); + if (appConfig.getString(AppConfig.PROP_ASSIGNMENT_RULE).length() > 14) { + String rule = appConfig.getString(AppConfig.PROP_ASSIGNMENT_RULE); if (rule.length() > 15) { rule = rule.substring(0, 15); } - getClient().setAssignmentRuleHeader(rule, false); + getConnection().setAssignmentRuleHeader(rule, false); } // field truncation - getClient().setAllowFieldTruncationHeader(config.getBoolean(Config.TRUNCATE_FIELDS)); + getConnection().setAllowFieldTruncationHeader(appConfig.getBoolean(AppConfig.PROP_TRUNCATE_FIELDS)); // TODO: make this configurable - getClient().setDisableFeedTrackingHeader(true); + getConnection().setDisableFeedTrackingHeader(true); + + getConnection().setDuplicateRuleHeader( + appConfig.getBoolean(AppConfig.PROP_DUPLICATE_RULE_ALLOW_SAVE), + appConfig.getBoolean(AppConfig.PROP_DUPLICATE_RULE_INCLUDE_RECORD_DETAILS), + appConfig.getBoolean(AppConfig.PROP_DUPLICATE_RULE_RUN_AS_CURRENT_USER) + ); + return true; } @@ -274,7 +322,7 @@ public SaveResult[] loadUpdates(List dynaBeans) throws ConnectionExcep /** * @param dynaBeans * @return SaveResult array - * @throws ConnectionException + * @throws ConnectionExceptio */ public SaveResult[] loadInserts(List dynaBeans) throws ConnectionException { return runSaveOperation(dynaBeans, INSERT_OPERATION, true); @@ -282,7 +330,7 @@ public SaveResult[] loadInserts(List dynaBeans) throws ConnectionExcep } private SaveResult[] runSaveOperation(List dynaBeans, ClientOperation op, - boolean isInsert) throws ApiFault, ConnectionException { + boolean isInsert) throws ApiFault, ConnectionException { SaveResult[] sr = runOperation(op, getSobjects(dynaBeans, op.getName())); String saveMessage = isInsert ? "Client.itemCreated" : "Client.itemUpdated"; for (int j = 0; j < sr.length; j++) { @@ -293,25 +341,25 @@ private SaveResult[] runSaveOperation(List dynaBeans, ClientOperation< private SObject[] getSobjects(List dynaBeans, String opName) { try { - SObject[] sobjects = SforceDynaBean.getSObjectArray(controller, dynaBeans, config.getString(Config.ENTITY), - config.getBoolean(Config.INSERT_NULLS)); + SObject[] sobjects = SforceDynaBean.getSObjectArray(controller, dynaBeans, appConfig.getString(AppConfig.PROP_ENTITY), + appConfig.getBoolean(AppConfig.PROP_INSERT_NULLS)); logger.debug(Messages.getString("Client.arraySize") + sobjects.length); //$NON-NLS-1$ return sobjects; } catch (IllegalAccessException ex) { logger.error( - Messages.getFormattedString("Client.operationError", new String[] { opName, ex.getMessage() }), ex); //$NON-NLS-1$ + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ throw new RuntimeException(ex); } catch (InvocationTargetException ex) { logger.error( - Messages.getFormattedString("Client.operationError", new String[] { opName, ex.getMessage() }), ex); //$NON-NLS-1$ + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ throw new RuntimeException(ex); } catch (NoSuchMethodException ex) { logger.error( - Messages.getFormattedString("Client.operationError", new String[] { opName, ex.getMessage() }), ex); //$NON-NLS-1$ + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ throw new RuntimeException(ex); } catch (ParameterLoadException ex) { logger.error( - Messages.getFormattedString("Client.operationError", new String[] { opName, ex.getMessage() }), ex); //$NON-NLS-1$ + Messages.getFormattedString("Client.operationError", new String[]{opName, ex.getMessage()}), ex); //$NON-NLS-1$ throw new RuntimeException(ex); } } @@ -321,21 +369,33 @@ protected R runOperation(ClientOperation op, A arg) throws Connecti if (op != this.LOGIN_OPERATION && !isSessionValid()) { connect(); } - int totalAttempts = 1 + (this.enableRetries ? this.maxRetries : 0); ConnectionException connectionException = null; - for (int tryNum = 0; tryNum < totalAttempts; tryNum++) { - try { - R result = op.run(arg); - if (result == null) logger.info(Messages.getString("Client.resultNull")); //$NON-NLS-1$ - return result; - } catch (ConnectionException ex) { + try { + R result = op.run(arg); + if (result == null) + logger.info(Messages.getString("Client.resultNull")); //$NON-NLS-1$ + this.getSession().performedSessionActivity(); // reset session activity timer + return result; + } catch (ConnectionException ex) { + String exceptionMessage = ex.getMessage(); + if (ex instanceof LoginFault) { + LoginFault lf = (LoginFault)ex; + exceptionMessage = lf.getExceptionMessage(); + } + + logger.error( + Messages.getFormattedString( + "Client.operationError", new String[]{op.getName(), exceptionMessage}), ex); //$NON-NLS-1$ + if (ex instanceof ApiFault) { + ApiFault fault = (ApiFault)ex; + String faultMessage = fault.getExceptionMessage(); logger.error( Messages.getFormattedString( - "Client.operationError", new String[] { op.getName(), ex.getMessage() }), ex); //$NON-NLS-1$ - // check retries - if (!checkConnectionException(ex, op.getName(), tryNum)) throw ex; - connectionException = ex; + "Client.operationError", new String[]{op.getName(), faultMessage}), fault); //$NON-NLS-1$ + } + // check retries + connectionException = ex; } throw connectionException; } @@ -352,7 +412,7 @@ public DeleteResult[] loadDeletes(List dynaBeans) throws ConnectionExc String[] dels = new String[dynaBeans.size()]; for (int i = 0; i < dynaBeans.size(); i++) { dynaBean = dynaBeans.get(i); - String id = (String)dynaBean.get("Id"); //$NON-NLS-1$ + String id = (String) dynaBean.get("Id"); //$NON-NLS-1$ if (id == null) { id = ""; } @@ -361,7 +421,6 @@ public DeleteResult[] loadDeletes(List dynaBeans) throws ConnectionExc logger.debug(Messages.getString("Client.arraySize") + dels.length); //$NON-NLS-1$ - DeleteResult[] result = runOperation(DELETE_OPERATION, dels); for (int j = 0; j < result.length; j++) { @@ -369,10 +428,39 @@ public DeleteResult[] loadDeletes(List dynaBeans) throws ConnectionExc } return result; } + + /** + * @param dynaBeans + * @return UndeleteResult array + * @throws ConnectionException + */ + public UndeleteResult[] loadUndeletes(List dynaBeans) throws ConnectionException { + + + DynaBean dynaBean; + String[] undels = new String[dynaBeans.size()]; + for (int i = 0; i < dynaBeans.size(); i++) { + dynaBean = dynaBeans.get(i); + String id = (String) dynaBean.get("Id"); //$NON-NLS-1$ + if (id == null) { + id = ""; + } + undels[i] = id; + } + logger.debug(Messages.getString("Client.arraySize") + undels.length); //$NON-NLS-1$ + + + UndeleteResult[] result = runOperation(UNDELETE_OPERATION, undels); + + for (int j = 0; j < result.length; j++) { + processResult(result[j].isSuccess(), "Client.itemUndeleted", result[j].getId(), result[j].getErrors(), j); + } + return result; + } /** * Query next batch of records using the query cursor - * + * * @param soql * @return query results * @throws ConnectionException @@ -383,7 +471,7 @@ public QueryResult queryMore(String soql) throws ConnectionException { /** * Query objects excluding the deleted objects - * + * * @param soql * @return query results * @throws ConnectionException @@ -394,7 +482,7 @@ public QueryResult query(String soql) throws ConnectionException { /** * Query objects including the deleted objects - * + * * @param soql * @return query results */ @@ -405,7 +493,7 @@ public QueryResult queryAll(String soql) throws ConnectionException { /** * Process result of a change operation that returns data success / errors (examples of operations with such * results: insert, update, upsert, delete, merge) - * + * * @param success * True if result is success * @param successMsgKey @@ -425,37 +513,48 @@ private void processResult(boolean success, String successMsgKey, String id, Err for (Error err : errors) { int startRow; try { - startRow = config.getInt(Config.LOAD_ROW_TO_START_AT); + startRow = appConfig.getInt(AppConfig.PROP_LOAD_ROW_TO_START_AT); } catch (ParameterLoadException e) { startRow = 0; } logger.error(Messages.getString("Client.itemError") //$NON-NLS-1$ - + new Integer((itemNbr + startRow)).toString()); + + Integer.valueOf(itemNbr + startRow).toString()); logger.error(Messages.getString("Client.errorCode") + err.getStatusCode().toString()); //$NON-NLS-1$ logger.error(Messages.getString("Client.errorMessage") + err.getMessage()); //$NON-NLS-1$ } } } - @Override - public PartnerConnection getClient() { - return this.client; - } - public Map getDescribeGlobalResults() { - return describeGlobalResults; - } - - Map getEntityDescribeMap() { - return this.entityDescribes; + if (this.describeGlobalResults == null || !appConfig.getBoolean(AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS)) { + this.describeGlobalResultsMap.clear(); + try { + this.describeGlobalResults = runOperation(DESCRIBE_GLOBAL_OPERATION, null); + } catch (ConnectionException e) { + logger.error("Failed to get description of sobjects", e.getMessage()); + return null; + } + } + + if (this.describeGlobalResultsMap.isEmpty()) { + for (DescribeGlobalSObjectResult res : describeGlobalResults.getSobjects()) { + if (res != null) { + if (res.getLabel().startsWith("__MISSING LABEL__")) { + res.setLabel(res.getName()); + } + this.describeGlobalResultsMap.put(res.getName(), res); + } + } + } + return describeGlobalResultsMap; } - DescribeGlobalResult getEntityTypes() { - return entityTypes; + private Map getCachedEntityDescribeMap() { + return this.entityFieldDescribesMap; } public DescribeSObjectResult getFieldTypes() { - String entity = this.config.getString(Config.ENTITY); + String entity = this.appConfig.getString(AppConfig.PROP_ENTITY); try { return describeSObject(entity); } catch (ConnectionException e) { @@ -463,33 +562,105 @@ public DescribeSObjectResult getFieldTypes() { } } - public Map getReferenceDescribes() { - return referenceDescribes; + public ReferenceEntitiesDescribeMap getReferenceDescribes() { + return referenceEntitiesDescribesMap; + } + + public LimitInfo getAPILimitInfo() { + LimitInfoHeader_element limitInfoElement = getConnection().getLimitInfoHeader(); + for (LimitInfo info : limitInfoElement.getLimitInfo()) { + if ("API REQUESTS".equalsIgnoreCase(info.getType())) { + return info; + } + } + return null; } boolean isSessionValid() { - if (config.getBoolean(Config.SFDC_INTERNAL) && config.getBoolean(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN)) { return true; } - if (config.getString(Config.OAUTH_ACCESSTOKEN) != null && config.getString(Config.OAUTH_ACCESSTOKEN).trim().length() > 0) { return true; } + if (appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL) && appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN)) { + return true; + } + if (appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN) != null && appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN).trim().length() > 0) { + return true; + } return isLoggedIn(); } private boolean login() throws ConnectionException, ApiFault { + return loginWithRetries(0); + } + + private static final int MAX_LOGIN_RETRIES = 2; + private boolean loginWithRetries(int currentRetryCount) throws ConnectionException, ApiFault { + if (currentRetryCount > MAX_LOGIN_RETRIES) { + return false; + } disconnect(); + String origEndpoint = new String(appConfig.getAuthEndpointForCurrentEnv()); + try { + dologin(); + logger.debug("able to successfully invoke server APIs of version " + getAPIVersionForTheSession()); + } catch (UnexpectedErrorFault fault) { + if (fault.getExceptionCode() == ExceptionCode.UNSUPPORTED_API_VERSION) { + logger.error("Failed to successfully invoke server APIs of version " + getAPIVersionForTheSession()); + setAPIVersionForTheSession(getPreviousAPIVersionInWSC()); + loginWithRetries(currentRetryCount++); + } else { + logger.error("Failed to get user info using manually configured session id", fault); + throw fault; + } + } catch (ConnectionException e) { + String exceptionMessage = e.getMessage(); + if (e instanceof LoginFault) { + LoginFault lf = (LoginFault)e; + exceptionMessage = lf.getExceptionMessage(); + } + logger.warn(Messages.getMessage(this.getClass(), "failedUsernamePasswordAuth", + origEndpoint, appConfig.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT), exceptionMessage)); + if (!appConfig.isDefaultAuthEndpointForCurrentEnv(origEndpoint)) { + // retry with default endpoint URL only if user is attempting production login + appConfig.setAuthEndpointForCurrentEnv(appConfig.getDefaultAuthEndpointForCurrentEnv()); + logger.info(Messages.getMessage(this.getClass(), "retryUsernamePasswordAuth", appConfig.getDefaultAuthEndpointForCurrentEnv(), appConfig.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT))); + loginWithRetries(currentRetryCount++); + } else { + logger.error("Failed to get user info using manually configured session id", e); + throw e; + } + } finally { + // restore original value of Config.ENDPOINT property + appConfig.setAuthEndpointForCurrentEnv(origEndpoint); + } + return true; // exception thrown if there is an issue with login + } + + private boolean dologin() throws ConnectionException, ApiFault { // Attempt the login giving the user feedback logger.info(Messages.getString("Client.sforceLogin")); //$NON-NLS-1$ - final ConnectorConfig cc = getLoginConnectorConfig(); - final PartnerConnection conn = Connector.newConnection(cc); + ConnectorConfig cc = getLoginConnectorConfig(); + boolean savedIsTraceMessage = cc.isTraceMessage(); + cc.setTraceMessage(false); + PartnerConnection conn = Connector.newConnection(cc); // identify the client as dataloader - conn.setCallOptions(ClientBase.getClientName(this.config), null); + conn.setCallOptions(ClientBase.getClientName(this.appConfig), null); - String oauthAccessToken = config.getString(Config.OAUTH_ACCESSTOKEN); - if (oauthAccessToken != null && oauthAccessToken.trim().length() > 0){ - setConfiguredSessionId(conn, oauthAccessToken); - } else if (config.getBoolean(Config.SFDC_INTERNAL) && config.getBoolean(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN)) { - setConfiguredSessionId(conn, config.getString(Config.SFDC_INTERNAL_SESSION_ID)); - } else { - setSessionRenewer(conn); - loginInternal(conn); + String oauthAccessToken = appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN); + try { + if (oauthAccessToken != null && oauthAccessToken.trim().length() > 0) { + cc = getLoginConnectorConfig(appConfig.getString(AppConfig.PROP_OAUTH_INSTANCE_URL)); + savedIsTraceMessage = cc.isTraceMessage(); + cc.setTraceMessage(false); + conn = Connector.newConnection(cc); + conn = setConfiguredSessionId(conn, oauthAccessToken, null); + } else if (appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL) && appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN)) { + conn = setConfiguredSessionId(conn, appConfig.getString(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID), null); + } else { + setSessionRenewer(conn); + loginInternal(conn); + } + } catch (Exception ex) { + throw ex; + } finally { + cc.setTraceMessage(savedIsTraceMessage); } return true; @@ -505,52 +676,57 @@ public SessionRenewalHeader renewSession(ConnectorConfig connectorConfig) throws }); } - private void setConfiguredSessionId(final PartnerConnection conn, String sessionId) throws ConnectionException { + private PartnerConnection setConfiguredSessionId(PartnerConnection conn, String sessionId, GetUserInfoResult userInfo) throws ConnectionException { logger.info("Using manually configured session id to bypass login"); conn.setSessionHeader(sessionId); - try { - conn.getUserInfo(); // check to make sure we have a good connection - } catch (ConnectionException e) { - logger.error("Failed to get user info using manually configured session id", e); - throw e; + if (userInfo == null) { + userInfo = conn.getUserInfo(); // check to make sure we have a good connection } - loginSuccess(conn, getServerUrl(config.getString(Config.ENDPOINT))); + loginSuccess(conn, getAuthenticationHostDomainUrl(appConfig.getAuthEndpointForCurrentEnv()), userInfo); + return conn; } private void loginInternal(final PartnerConnection conn) throws ConnectionException, PasswordExpiredException { final ConnectorConfig cc = conn.getConfig(); + cc.setRequestHeader(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()); try { logger.info(Messages.getMessage(getClass(), "sforceLoginDetail", cc.getAuthEndpoint(), cc.getUsername())); LoginResult loginResult = runOperation(LOGIN_OPERATION, conn); // if password has expired, throw an exception - if (loginResult.getPasswordExpired()) { throw new PasswordExpiredException(Messages - .getString("Client.errorExpiredPassword")); //$NON-NLS-1$ + if (loginResult.getPasswordExpired()) { + throw new PasswordExpiredException(Messages + .getString("Client.errorExpiredPassword")); //$NON-NLS-1$ } // update session id and service endpoint based on response conn.setSessionHeader(loginResult.getSessionId()); String serverUrl = loginResult.getServerUrl(); - String server = getServerUrl(serverUrl); - if (config.getBoolean(Config.RESET_URL_ON_LOGIN)) { + String server = getAuthenticationHostDomainUrl(serverUrl); + if (appConfig.getBoolean(AppConfig.PROP_RESET_URL_ON_LOGIN)) { cc.setServiceEndpoint(serverUrl); } - loginSuccess(conn, server); + loginSuccess(conn, server, loginResult.getUserInfo()); } catch (ConnectionException ex) { - logger.error(Messages.getMessage(getClass(), "loginError", cc.getAuthEndpoint(), ex.getMessage()), ex); + String exceptionMessage = ex.getMessage(); + if (ex instanceof LoginFault) { + LoginFault lf = (LoginFault)ex; + exceptionMessage = lf.getExceptionMessage(); + } + logger.error(Messages.getMessage(getClass(), "loginError", cc.getAuthEndpoint(), exceptionMessage), ex); throw ex; } } - private void loginSuccess(PartnerConnection conn, String serv) { - this.client = conn; - setSession(conn.getSessionHeader().getSessionId(), serv); + private void loginSuccess(PartnerConnection conn, String serv, GetUserInfoResult userInfo) { + setConnection(conn); + setSession(conn.getSessionHeader().getSessionId(), serv, userInfo); } private String getServerStringFromUrl(URL url) { return url.getProtocol() + "://" + url.getAuthority(); } - private String getServerUrl(String serverUrl) { - if (config.getBoolean(Config.RESET_URL_ON_LOGIN)) { + private String getAuthenticationHostDomainUrl(String serverUrl) { + if (appConfig.getBoolean(AppConfig.PROP_RESET_URL_ON_LOGIN)) { try { return getServerStringFromUrl(new URL(serverUrl)); } catch (MalformedURLException e) { @@ -558,13 +734,16 @@ private String getServerUrl(String serverUrl) { throw new RuntimeException(e); } } - return getDefaultServer(); + // Either RESET_URL_ON_LOGIN is false or input param serverURL + // is invalid. Try returning configured Auth endpoint instead. + return appConfig.getAuthEndpointForCurrentEnv(); } public boolean logout() { try { - PartnerConnection pc = getClient(); + PartnerConnection pc = getConnection(); if (pc != null) pc.logout(); + } catch (ConnectionException e) { // ignore } finally { @@ -575,108 +754,138 @@ public boolean logout() { public void disconnect() { clearSession(); - this.client = null; - } - - /** - * @param operationName - */ - private void retrySleep(String operationName, int retryNum) { - int sleepSecs; - try { - sleepSecs = config.getInt(Config.MIN_RETRY_SLEEP_SECS); - } catch (ParameterLoadException e1) { - sleepSecs = Config.DEFAULT_MIN_RETRY_SECS; - } - // sleep between retries is based on the retry attempt #. Sleep for longer periods with each retry - sleepSecs = sleepSecs + (retryNum * 10); // sleep for MIN_RETRY_SLEEP_SECS + 10, 20, 30, etc. - - logger.info(Messages.getFormattedString("Client.retryOperation", new String[] { Integer.toString(retryNum + 1), - operationName, Integer.toString(sleepSecs) })); - try { - Thread.sleep(sleepSecs * 1000); - } catch (InterruptedException e) { // ignore - } - } - - /** - * Gets the sObject describes for all entities - */ - public boolean setEntityDescribes() throws ConnectionException { - setEntityTypes(); - if (this.describeGlobalResults.isEmpty()) { - for (DescribeGlobalSObjectResult res : entityTypes.getSobjects()) { - if (res != null) this.describeGlobalResults.put(res.getName(), res); - } - } - - return true; - } - - /** - * Gets the available objects from the global describe - */ - private void setEntityTypes() throws ConnectionException { - if (this.entityTypes == null) - this.entityTypes = runOperation(DESCRIBE_GLOBAL_OPERATION, null); + setConnection(null); } /** * Set the map of references to object external id info for current entity - * + * * @throws ConnectionException */ public void setFieldReferenceDescribes() throws ConnectionException { - referenceDescribes.clear(); - if (getDescribeGlobalResults().isEmpty()) { - setEntityDescribes(); - } if (getFieldTypes() == null) { setFieldTypes(); } + Collection mappedSFFields = null; if (getDescribeGlobalResults() != null) { - Field[] entityFields = getFieldTypes().getFields(); - for (Field entityField : entityFields) { - // upsert on references (aka foreign keys) is supported only - // 1. When field has relationship is set and refers to exactly one object - // 2. When field is either createable or updateable. If neither is true, upsert will never work for that - // relationship. - if (entityField.isCreateable() || entityField.isUpdateable()) { - String relationshipName = entityField.getRelationshipName(); - String[] referenceTos = entityField.getReferenceTo(); - if (referenceTos != null && referenceTos.length == 1 && referenceTos[0] != null - && relationshipName != null && relationshipName.length() > 0 - && (entityField.isCreateable() || entityField.isUpdateable())) { - - String refEntityName = referenceTos[0]; - - // make sure that the object is legal to upsert - Field[] refObjectFields = describeSObject(refEntityName).getFields(); - Map refFieldInfo = new HashMap(); - for (Field refField : refObjectFields) { - if (refField.isExternalId()) { - refField.setCreateable(entityField.isCreateable()); - refField.setUpdateable(entityField.isUpdateable()); - refFieldInfo.put(refField.getName(), refField); + String operation = appConfig.getString(AppConfig.PROP_OPERATION); + if (AppUtil.getAppRunMode() == AppUtil.APP_RUN_MODE.BATCH + && operation != null + && !operation.isBlank() + && appConfig.getOperationInfo() != OperationInfo.extract + && appConfig.getOperationInfo() != OperationInfo.extract_all) { + // import operation in batch mode + LoadMapper mapper = (LoadMapper)controller.getMapper(); + // call describe only for mapped fields + mappedSFFields = mapper.getDestColumns(); + } + setFieldReferenceDescribes(mappedSFFields); + } + } + + public void setFieldReferenceDescribes(Collection sfFields) throws ConnectionException { + if (appConfig.getBoolean(AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS)) { + referenceEntitiesDescribesMap = parentDescribeCache.get(appConfig.getString(AppConfig.PROP_ENTITY)); + } else { + referenceEntitiesDescribesMap.clear(); + } + if (referenceEntitiesDescribesMap == null) { + referenceEntitiesDescribesMap = new ReferenceEntitiesDescribeMap(this); + } + if (referenceEntitiesDescribesMap.size() > 0) { + return; // use cached value + } + Field[] entityFields = getFieldTypes().getFields(); + boolean useMappedLookupRelationshipNamesForRefDescribes = false; + ArrayList relFieldsNeedingRefDescribes = new ArrayList(); + if (sfFields != null && !sfFields.isEmpty()) { + useMappedLookupRelationshipNamesForRefDescribes = true; + for (String mappedFieldList : sfFields) { + String[]mappedFields = mappedFieldList.split(","); + for (String field : mappedFields) { + try { + ParentIdLookupFieldFormatter lookupFieldFormatter = new ParentIdLookupFieldFormatter(field); + if (lookupFieldFormatter.getParent() != null + && lookupFieldFormatter.getParentFieldName() != null) { + String relationshipNameInMappedField = lookupFieldFormatter.getParent().getRelationshipName(); + if (relationshipNameInMappedField == null) { + useMappedLookupRelationshipNamesForRefDescribes = false; + break; + } else { + relFieldsNeedingRefDescribes.add(relationshipNameInMappedField); } } - if (!refFieldInfo.isEmpty()) { - DescribeRefObject describe = new DescribeRefObject(refEntityName, refFieldInfo); - referenceDescribes.put(relationshipName, describe); - } + } catch (RelationshipFormatException e) { + // do not optimize getting lookup field describes + useMappedLookupRelationshipNamesForRefDescribes = false; + break; } } + if (!useMappedLookupRelationshipNamesForRefDescribes) { + relFieldsNeedingRefDescribes.clear(); + break; + } } } + + for (Field childObjectField : entityFields) { + // verify that the sobject field represents a relationship field where + // the sobject is a child with one or more parent sobjects. + String[] parentObjectNames = childObjectField.getReferenceTo(); + String relationshipName = childObjectField.getRelationshipName(); + if (parentObjectNames == null || parentObjectNames.length == 0 || parentObjectNames[0] == null + || relationshipName == null || relationshipName.length() == 0 + || (!childObjectField.isCreateable() && !childObjectField.isUpdateable())) { + // parent-child relationship either does not exist or + // it is neither modifiable nor updateable. + continue; + } + if (!useMappedLookupRelationshipNamesForRefDescribes + || relFieldsNeedingRefDescribes.contains(relationshipName)) { + processParentObjectArrayForLookupReferences(parentObjectNames, childObjectField); + } + } + if (appConfig.getBoolean(AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS) + && sfFields == null) { + // got the full list of parents' describes for an sobject + parentDescribeCache.put(appConfig.getString(AppConfig.PROP_ENTITY), referenceEntitiesDescribesMap); + } + } + + private void processParentObjectArrayForLookupReferences(String[] parentObjectNames, Field childObjectField) throws ConnectionException { + for (int parentObjectIndex = 0; parentObjectIndex < parentObjectNames.length; parentObjectIndex++ ) { + String parentObjectName = parentObjectNames[parentObjectIndex]; + processParentObjectForLookupReferences(parentObjectName, childObjectField, parentObjectIndex, parentObjectNames.length); + } + } + + private void processParentObjectForLookupReferences(String parentObjectName, Field childObjectField, int parentObjectIndex, int numParentTypes) throws ConnectionException { + Field[] parentObjectFields = describeSObject(parentObjectName).getFields(); + Map parentIdLookupFieldMap = new HashMap(); + for (Field parentField : parentObjectFields) { + processParentFieldForLookupReference(parentField, childObjectField, numParentTypes, parentObjectIndex, numParentTypes, parentIdLookupFieldMap); + } + if (!parentIdLookupFieldMap.isEmpty()) { + DescribeRefObject describeRelationship = new DescribeRefObject(parentObjectName, childObjectField, parentIdLookupFieldMap); + referenceEntitiesDescribesMap.put(childObjectField.getRelationshipName(), describeRelationship); + } + } + + private void processParentFieldForLookupReference(Field parentField, Field childObjectField, int numParentTypes, int parentObjectIndex, int totalParentObjects, Map parentIdLookupFieldMap) { + if (!parentField.isIdLookup()) { + return; + } + parentIdLookupFieldMap.put(parentField.getName(), parentField); + } /** * Gets the sobject describe for the given entity - * + * * @throws ConnectionException */ public void setFieldTypes() throws ConnectionException { - describeSObject(config.getString(Config.ENTITY)); + describeSObject(appConfig.getString(AppConfig.PROP_ENTITY)); } /** @@ -691,96 +900,128 @@ public void validateSession() { } @Override - protected ConnectorConfig getConnectorConfig() { + public ConnectorConfig getConnectorConfig() { ConnectorConfig cc = super.getConnectorConfig(); cc.setManualLogin(true); return cc; } - - private ConnectorConfig getLoginConnectorConfig() { - ConnectorConfig cc = getConnectorConfig(); - String serverUrl = getDefaultServer(); - cc.setAuthEndpoint(serverUrl + DEFAULT_AUTH_ENDPOINT_URL.getPath()); - cc.setServiceEndpoint(serverUrl + DEFAULT_AUTH_ENDPOINT_URL.getPath()); - return cc; + + public static String getServicePath() { + return "/services/Soap/u/" + getAPIVersionForTheSession() + "/"; } - private String getDefaultServer() { - String serverUrl = config.getString(Config.ENDPOINT); - if (serverUrl == null || serverUrl.length() == 0) { - serverUrl = getServerStringFromUrl(DEFAULT_AUTH_ENDPOINT_URL); - } - return serverUrl; + private synchronized ConnectorConfig getLoginConnectorConfig() { + return getLoginConnectorConfig(appConfig.getAuthEndpointForCurrentEnv()); + } + + private synchronized ConnectorConfig getLoginConnectorConfig(String serverURL) { + this.connectorConfig = getConnectorConfig(); + this.connectorConfig.setAuthEndpoint(serverURL + getServicePath()); + this.connectorConfig.setServiceEndpoint(serverURL + getServicePath()); + return this.connectorConfig; } /** * This function returns the describe call for an sforce entity - * + * * @return DescribeSObjectResult * @throws ConnectionException */ public DescribeSObjectResult describeSObject(String entity) throws ConnectionException { - DescribeSObjectResult result = getEntityDescribeMap().get(entity); + DescribeSObjectResult result = null; + if (appConfig.getBoolean(AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS)) { + result = getCachedEntityDescribeMap().get(entity); + } if (result == null) { result = runOperation(DESCRIBE_SOBJECT_OPERATION, entity); if (result != null) { - getEntityDescribeMap().put(result.getName(), result); + getCachedEntityDescribeMap().put(result.getName(), result); } } return result; } - - /** - * Checks whether retry makes sense for the given exception and given the number of current vs. max retries. If - * retry makes sense, then before returning, this method will put current thread to sleep before allowing another - * retry. - * - * @param ex - * @param operationName - * @return true if retry should be executed for operation. false if there's no retry. - */ - private boolean checkConnectionException(ConnectionException ex, String operationName, int retryNum) { - if (!this.enableRetries) return false; - final String msg = ex.getMessage(); - if (msg != null && msg.toLowerCase().indexOf("connection reset") >= 0) { - retrySleep(operationName, retryNum); - return true; + + public Field[] getSObjectFieldAttributesForRow(String sObjectName, TableRow dataRow) throws ConnectionException { + ArrayList attributesForRow = new ArrayList(); + DescribeSObjectResult entityDescribe = describeSObject(sObjectName); + for (String headerColumnName : dataRow.getHeader().getColumns()) { + Field[] fieldAttributesArray = entityDescribe.getFields(); + for (Field fieldAttributes : fieldAttributesArray) { + if (fieldAttributes.getName().equalsIgnoreCase(headerColumnName)) { + attributesForRow.add(fieldAttributes); + } + } } - return false; + return attributesForRow.toArray(new Field[1]); } private final Map fieldsByName = new HashMap(); - public Field getField(String apiName) { - apiName = apiName.toLowerCase(); - Field field = this.fieldsByName.get(apiName); + public Field getField(String sObjectFieldName) { + Field field = this.fieldsByName.get(sObjectFieldName); if (field == null) { - field = lookupField(apiName); - this.fieldsByName.put(apiName, field); + field = lookupField(sObjectFieldName); + this.fieldsByName.put(sObjectFieldName, field); } return field; } - - private Field lookupField(String apiName) { - // look for field on target object + + public Field getFieldFromRelationshipName(String relationshipName) { for (Field f : getFieldTypes().getFields()) { - if (apiName.equals(f.getName().toLowerCase()) || apiName.equals(f.getLabel().toLowerCase())) return f; - } - // look for reference field on target object - if (apiName.contains(":")) { - Map refs = getReferenceDescribes(); - for (Map.Entry ent : refs.entrySet()) { - String relName = ent.getKey().toLowerCase(); - if (apiName.startsWith(relName)) { - for (Map.Entry refEntry : ent.getValue().getFieldInfoMap().entrySet()) { - String thisRefName = relName + ":" + refEntry.getKey().toLowerCase(); - if (apiName.equals(thisRefName)) return refEntry.getValue(); - } + if (f.getReferenceTo().length > 0) { + String relName = f.getRelationshipName(); + if (relName != null + && !relName.isBlank() + && relName.equalsIgnoreCase(relationshipName)) { + return f; } } } return null; } + + // sObjectFieldName could be sObject's field name + // or a reference to parent sObject's field name in the old or new format. + private Field lookupField(String sObjectFieldName) { + for (Field f : getFieldTypes().getFields()) { + if (sObjectFieldName.equalsIgnoreCase(f.getName()) || sObjectFieldName.equalsIgnoreCase(f.getLabel())) { + return f; + } + ParentIdLookupFieldFormatter parentLookupFieldFormatter = null; + try { + parentLookupFieldFormatter = new ParentIdLookupFieldFormatter(sObjectFieldName); + } catch (RelationshipFormatException ex) { + // ignore + } + if (parentLookupFieldFormatter != null) { + if (!parentLookupFieldFormatter.getParent().getRelationshipName().equalsIgnoreCase(f.getRelationshipName())) { + continue; + } + Field parentField = this.referenceEntitiesDescribesMap.getParentField(sObjectFieldName); + if (parentField != null) { + return parentField; + } + String parentSObjectName = parentLookupFieldFormatter.getParent().getParentObjectName(); + if (parentSObjectName == null || parentSObjectName.isBlank()) { + parentSObjectName = f.getReferenceTo()[0]; + } + if (parentSObjectName == null || parentSObjectName.isBlank()) { + // something is wrong for a relationship field + logger.error("Field " + f.getName() + " does not have a parent sObject"); + continue; + } + // need to add the relationship mapping to referenceEntitiesDescribesMap + try { + processParentObjectForLookupReferences(parentSObjectName, f, 0, 1); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return this.referenceEntitiesDescribesMap.getParentField(sObjectFieldName); + } + } + return this.referenceEntitiesDescribesMap.getParentField(sObjectFieldName); + } } diff --git a/src/main/java/com/salesforce/dataloader/client/RESTClient.java b/src/main/java/com/salesforce/dataloader/client/RESTClient.java new file mode 100644 index 000000000..14452e57a --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/RESTClient.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.client; + +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.controller.Controller; +import com.sforce.ws.ConnectorConfig; + +public abstract class RESTClient extends ClientBase { + + public RESTClient(Controller controller, Logger logger) { + super(controller, logger); + // TODO Auto-generated constructor stub + } + + @Override + public synchronized ConnectorConfig getConnectorConfig() { + ConnectorConfig connectorConfig = super.getConnectorConfig(); + // override the restEndpoint value set in the superclass + String server = getSession().getServer(); + if (server != null) { + connectorConfig.setRestEndpoint(server + getServiceURLPath()); + } + return connectorConfig; + } + + public abstract String getServiceURLPath(); +} diff --git a/src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java b/src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java new file mode 100644 index 000000000..8777b041d --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/ReferenceEntitiesDescribeMap.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.client; + +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.dyna.ParentIdLookupFieldFormatter; +import com.salesforce.dataloader.dyna.ParentSObjectFormatter; +import com.salesforce.dataloader.exception.RelationshipFormatException; +import com.sforce.soap.partner.Field; + +/** + * + */ +public class ReferenceEntitiesDescribeMap { + + private Map referenceEntitiesDescribeMap = new HashMap(); + private static final Logger logger = DLLogManager.getLogger(ReferenceEntitiesDescribeMap.class); + private PartnerClient client = null; + /** + * + */ + public ReferenceEntitiesDescribeMap(PartnerClient client) { + this.client = client; + } + + public void put(String relationshipFieldName, DescribeRefObject parent) { + ParentSObjectFormatter objField = null; + try { + objField = new ParentSObjectFormatter(parent.getParentObjectName(), relationshipFieldName); + } catch (RelationshipFormatException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return; + } + referenceEntitiesDescribeMap.put(objField.toString(), parent); + } + + public void clear() { + this.referenceEntitiesDescribeMap.clear(); + } + + public int size() { + return this.referenceEntitiesDescribeMap.size(); + } + + public Set keySet() { + return this.referenceEntitiesDescribeMap.keySet(); + } + + // fieldName could be in the old format that assumes single parent: + // : + // + // fieldName could also be in the new format + // :. + public Field getParentField(String fieldName) { + ParentIdLookupFieldFormatter lookupFieldStr = null; + try { + lookupFieldStr = new ParentIdLookupFieldFormatter(fieldName); + } catch (RelationshipFormatException e) { + logger.error(e.getMessage()); + } + if (lookupFieldStr == null + || lookupFieldStr.getParentFieldName() == null + || lookupFieldStr.getParent().getRelationshipName() == null) { + return null; + } else { + DescribeRefObject parent = getParentSObject(lookupFieldStr.getParent()); + if (parent == null) { + return null; + } + for (Map.Entry refEntry : parent.getParentObjectFieldMap().entrySet()) { + if (lookupFieldStr.getParentFieldName().equalsIgnoreCase(refEntry.getKey())) { + return refEntry.getValue(); + } + } + return null; + } + } + + // fieldName could be in the old format that assumes single parent: + // : + // + // fieldName could also be in the new format + // :. + + public DescribeRefObject getParentSObject(String lookupFieldName) { + try { + return getParentSObject(new ParentSObjectFormatter(lookupFieldName)); + } catch (RelationshipFormatException e) { + logger.error(e.getMessage()); + } + return null; + } + + private DescribeRefObject getParentSObject(ParentSObjectFormatter parentFormatter) { + if (parentFormatter == null || parentFormatter.getRelationshipName() == null) { + return null; + } + String parentObjName = parentFormatter.getParentObjectName(); + if (parentObjName == null) { + Field relationshipField = client.getFieldFromRelationshipName(parentFormatter.getRelationshipName()); + if (relationshipField == null) { + return null; + } + parentObjName = relationshipField.getReferenceTo()[0]; + parentFormatter.setParentObjectName(parentObjName); + } + for (Map.Entry ent : referenceEntitiesDescribeMap.entrySet()) { + if (parentFormatter.matches(ent.getKey())) { + return ent.getValue(); + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/client/SObject4JSON.java b/src/main/java/com/salesforce/dataloader/client/SObject4JSON.java new file mode 100644 index 000000000..78278d120 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/SObject4JSON.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.client; + +import java.util.HashMap; +import java.util.Map; + +public class SObject4JSON { + private String type; + private HashMap fields = new HashMap(); + + public SObject4JSON(String type) { + this.type = type; + } + + public String getType() { + return type; + } + + public HashMap getFields() { + return fields; + } + + public void setField(String fieldName, Object fieldValue) { + this.fields.put(fieldName, fieldValue); + } + + public Object getField(String fieldName) { + return this.fields.get(fieldName); + } + + public Map getRepresentationForCompositeREST() { + HashMap jsonMap = new HashMap(); + HashMap attributesMap = new HashMap(); + attributesMap.put("type", this.type); + jsonMap.put("attributes", attributesMap); + jsonMap.putAll(this.fields); + return jsonMap; + } +} diff --git a/src/main/java/com/salesforce/dataloader/client/SessionInfo.java b/src/main/java/com/salesforce/dataloader/client/SessionInfo.java index 9a19ad836..e41443cbd 100644 --- a/src/main/java/com/salesforce/dataloader/client/SessionInfo.java +++ b/src/main/java/com/salesforce/dataloader/client/SessionInfo.java @@ -25,7 +25,11 @@ */ package com.salesforce.dataloader.client; +import java.util.Calendar; +import com.sforce.soap.partner.GetUserInfoResult; + +@SuppressWarnings("serial") public class SessionInfo { public static class NotLoggedInException extends RuntimeException { @@ -36,18 +40,28 @@ private NotLoggedInException(SessionInfo sess) { private final String sessionId; private final String serverUrl; + private final GetUserInfoResult userInfo; + private long lastActivityTimeInMsec = 0; - SessionInfo(String sessionId, String server) { + SessionInfo(String sessionId, String server, GetUserInfoResult userInfo) { this.sessionId = sessionId; this.serverUrl = server; + this.userInfo = userInfo; + if (this.userInfo != null) { + this.lastActivityTimeInMsec = Calendar.getInstance().getTimeInMillis(); + } } SessionInfo() { - this(null, null); + this(null, null, null); } public boolean isSessionValid() { - return this.sessionId != null; + long currentTimeInMsec = Calendar.getInstance().getTimeInMillis(); + long inSessionElapsedTimeInSec = (currentTimeInMsec - this.lastActivityTimeInMsec)/1000; + return (this.sessionId != null + && userInfo != null + && inSessionElapsedTimeInSec < userInfo.getSessionSecondsValid()); } public void validate() throws NotLoggedInException { @@ -61,4 +75,12 @@ public String getSessionId() { public String getServer() { return this.serverUrl; } + + public GetUserInfoResult getUserInfoResult() { + return this.userInfo; + } + + public void performedSessionActivity() { + this.lastActivityTimeInMsec = Calendar.getInstance().getTimeInMillis(); + } } diff --git a/src/main/java/com/salesforce/dataloader/client/SimplePost.java b/src/main/java/com/salesforce/dataloader/client/SimplePost.java index 78fb3f49d..a8a0b8757 100644 --- a/src/main/java/com/salesforce/dataloader/client/SimplePost.java +++ b/src/main/java/com/salesforce/dataloader/client/SimplePost.java @@ -30,6 +30,9 @@ import java.io.IOException; import java.io.InputStream; +import org.apache.http.Header; +import org.apache.http.message.BasicNameValuePair; + /** * Created by rmazzeo on 12/9/15. */ @@ -43,4 +46,8 @@ public interface SimplePost { int getStatusCode(); String getReasonPhrase(); + + Header[] getResponseHeaders(String headerName); + + void addBasicNameValuePair(BasicNameValuePair pair); } diff --git a/src/main/java/com/salesforce/dataloader/client/SimplePostFactory.java b/src/main/java/com/salesforce/dataloader/client/SimplePostFactory.java index 1925e53b9..4d8c083cc 100644 --- a/src/main/java/com/salesforce/dataloader/client/SimplePostFactory.java +++ b/src/main/java/com/salesforce/dataloader/client/SimplePostFactory.java @@ -25,7 +25,7 @@ */ package com.salesforce.dataloader.client; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import org.apache.http.message.BasicNameValuePair; import java.util.function.Function; @@ -35,10 +35,10 @@ */ public class SimplePostFactory { - private static Function constructor = c -> new DefaultSimplePost(c.config, c.endpoint, c.pairs);; + private static Function constructor = c -> new DefaultSimplePost(c.appConfig, c.endpoint, c.pairs);; public static class Criteria{ - public Config config; + public AppConfig appConfig; public String endpoint; public BasicNameValuePair[] pairs; } @@ -53,9 +53,9 @@ public static void setConstructor(Function constructor) SimplePostFactory.constructor = constructor; } - public static SimplePost getInstance(Config config, String endpoint, BasicNameValuePair... pairs){ + public static SimplePost getInstance(AppConfig appConfig, String endpoint, BasicNameValuePair... pairs){ Criteria criteria = new Criteria(); - criteria.config = config; + criteria.appConfig = appConfig; criteria.endpoint = endpoint; criteria.pairs = pairs; diff --git a/src/main/java/com/salesforce/dataloader/client/TransportFactoryImpl.java b/src/main/java/com/salesforce/dataloader/client/TransportFactoryImpl.java new file mode 100644 index 000000000..67768ae1b --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/client/TransportFactoryImpl.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.client; + +import com.sforce.ws.transport.Transport; +import com.sforce.ws.transport.TransportFactory; + +public class TransportFactoryImpl implements TransportFactory { + + public TransportFactoryImpl() { + } + + @Override + public Transport createTransport() { + return HttpClientTransport.getInstance(); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/config/AppConfig.java b/src/main/java/com/salesforce/dataloader/config/AppConfig.java new file mode 100644 index 000000000..57751d8f9 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/config/AppConfig.java @@ -0,0 +1,2006 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.config; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.exception.ConfigInitializationException; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.exception.ProcessInitializationException; +import com.salesforce.dataloader.security.EncryptionAesUtil; +import com.salesforce.dataloader.util.AppUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintStream; +import java.io.PrintWriter; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.security.GeneralSecurityException; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.StringJoiner; +import java.util.TimeZone; + +import javax.xml.parsers.FactoryConfigurationError; + +import com.salesforce.dataloader.util.DLLogManager; +import com.salesforce.dataloader.util.LoggingUtil; + +import org.apache.logging.log4j.Logger; + +/** + * @author Lexi Viripaeff + * @since 6.0 + * + * **** README ***** + * + * Config class assimilates all properties including: + * - properties specified in config.properties file + * - properties specified in process-conf.xml file + * - properties specified as command line arguments + * - properties specified in Settings dialog - these get stored in config.properties file + * - properties set during operation steps (including login) and execution of an operation + * + * How properties are loaded, set in-memory, and saved to a file: + * - Properties loaded from and saved to config.properties file + * All properties except those that are defined with the prefix "CLI_OPTION_" + * can be specified in config.properties file. + * "Save" from Setup dialog saves modified properties in config.properties file. + * + * - Properties loaded from and saved to _lastRun.properties and ui_lastRun.properties files + * Contains 2 properties: timestamp of the last run and number of records processed during + * last upload (insert, upsert, update, delete, hard delete) operation if the operation + * was performed using Partner API. + * + * - Properties loaded from process-conf.xml file. Properties are not saved to process-conf.xml. + * Contains properties relevant to execute an operation in the batch mode. + * A property specified in process-conf.xml overrides the same property + * specified in config.properties. However, it DOES NOT overwrite any property + * in config.properties file. + * + * - Properties loaded from command line arguments + * Some properties can only be specified through command line arguments. These + * properties are defined with the prefix "CLI_OPTION_". A property set + * in command line argument overrides the value set in other files. + * + * - Properties set in Settings dialog and saved in config.properties file + * + * - Properties set during actions and saved in config.properties/_lastRun.properties file. + * Some properties are set during actions such as login, specifying results folder for an operation, and so on. + * For example, "process.operation" property is set when user clicks on the operation button. + * These properties may not get saved depending on whether they are designated as "read-only" properties. + * + * - Certain properties are "read-only" in that their value set during the app execution + * is never saved. + * For example, "sfdc.password" can be specified in config.properties, which + * may be overridden by a different value when the user logs in. However, its + * value specified in config.properties is not overridden when save() method is invoked. + * + */ +public class AppConfig { + private static Logger logger = DLLogManager.getLogger(AppConfig.class); + private static final String DECRYPTED_SUFFIX = ".decrypted"; + + /** + * Default values for specific parameters + */ + public static final int DEFAULT_EXPORT_BATCH_SIZE = 500; + public static final int MAX_EXPORT_BATCH_SIZE = 2000; + public static final int MIN_EXPORT_BATCH_SIZE = 200; + public static final int DEFAULT_MIN_RETRY_SECS = 2; + public static final int DEFAULT_MAX_RETRIES = 3; + public static final int MAX_RETRIES_LIMIT = 10; + public static final int DEFAULT_CONNECTION_TIMEOUT_SECS = 60; + public static final int DEFAULT_TIMEOUT_SECS = 540; + public static final int DEFAULT_NUM_ROWS_LOAD_BATCH = 200; + public static final int DEFAULT_DAO_WRITE_BATCH_SIZE = 500; + public static final int DEFAULT_DAO_READ_BATCH_SIZE = 200; + public static final int MAX_NUM_ROWS_SOAP_API_IMPORT_BATCH = 200; + public static final int MAX_DAO_READ_BATCH_SIZE = 200; + public static final int MAX_DAO_WRITE_BATCH_SIZE = 2000; + public static final int MAX_SOAP_API_IMPORT_BATCH_BYTES = 50000000; + public static final int MAX_REST_API_IMPORT_BATCH_BYTES = 50000000; + + // Bulk v1 and v2 limits specified at https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_bulkapi.htm + public static final int MAX_BULK_API_IMPORT_BATCH_BYTES = 10000000; + public static final int MAX_NUM_ROWS_BULK_API_IMPORT_BATCH = 10000; + public static final int MAX_BULKV2_API_IMPORT_JOB_BYTES = 150000000; + public static final int MAX_NUM_ROWS_BULKV2_API_IMPORT_JOB = 150000000; + public static final int DEFAULT_NUM_ROWS_BULK_API_IMPORT_BATCH = 2000; + + public static final long DEFAULT_BULK_API_CHECK_STATUS_INTERVAL = 5000L; + public static final String DEFAULT_ENDPOINT_URL_PROD = "https://login.salesforce.com/"; + public static final String DEFAULT_ENDPOINT_URL_SANDBOX = "https://test.salesforce.com/"; + public static final String LIGHTNING_ENDPOINT_URL_PART_VAL = "lightning.force.com"; + public static final String MYSF_ENDPOINT_URL_PART_VAL = "mysalesforce.com"; + public static final String SERVER_PROD_ENVIRONMENT_VAL = "Production"; + public static final String SERVER_SB_ENVIRONMENT_VAL = "Sandbox"; + + public static final String OAUTH_PROD_REDIRECTURI_VAL = "https://login.salesforce.com/services/oauth2/success"; + public static final String OAUTH_SB_REDIRECTURI_VAL = "https://test.salesforce.com/services/oauth2/success"; + + public static final String BULK_CLIENTID_VAL = "DataLoaderBulkUI/"; + public static final String PARTNER_CLIENTID_VAL = "DataLoaderPartnerUI/"; + + public static final int DEFAULT_BULK_QUERY_PK_CHUNK_SIZE = 100000; + public static final int MAX_BULK_QUERY_PK_CHUNK_SIZE = 250000; + + /* + * Issue #59 - Dataloader will not read all the database rows to get a total count + * when skipTotalCount = "false" + * + * The default is "true" + */ + public static final Boolean DEFAULT_SKIP_TOTAL_COUNT = true; + /** + * Constants that were made not configurable by choice + */ + public static final String ID_COLUMN_NAME = "ID"; //$NON-NLS-1$ + public static final String ID_COLUMN_NAME_IN_BULKV2 = "sf__id"; + public static final String ERROR_COLUMN_NAME = "ERROR"; //$NON-NLS-1$ + public static final String STATUS_COLUMN_NAME = "STATUS"; //$NON-NLS-1$ + public static final String STATUS_COLUMN_NAME_IN_BULKV2 = "sf__Created"; //$NON-NLS-1$ + + /** + * The mapping from preference name to preference value (represented as strings). + */ + private Properties loadedProperties = new LinkedProperties(); + private Properties readOnlyPropertiesFromPropertiesFile = new LinkedProperties(); + private final Properties defaultProperties; + private final boolean saveAllProps; + private Map configPropsMetadataMap = ConfigPropertyMetadata.getPropertiesMap(); + + private Map overridesMap; + + /** + * The default-default value for the respective types. + */ + public static final boolean BOOLEAN_DEFAULT = false; + public static final double DOUBLE_DEFAULT = 0.0; + public static final float FLOAT_DEFAULT = 0.0f; + public static final int INT_DEFAULT = 0; + public static final long LONG_DEFAULT = 0L; + public static final String STRING_DEFAULT = ""; //$NON-NLS-1$ + public static final Map MAP_STRING_DEFAULT = new LinkedHashMap<>(); + + /** + * The Constants for the current Loader Keys + */ + // + + // + // Configurable properties that can only be specified as CLI options + // + public static final String CLI_OPTION_INSTALLATION_CREATE_MACOS_APPS_FOLDER_SHORTCUT_PROP = "salesforce.installation.shortcut.macos.appsfolder"; + public static final String CLI_OPTION_INSTALLATION_CREATE_WINDOWS_START_MENU_SHORTCUT_PROP = "salesforce.installation.shortcut.windows.startmenu"; + public static final String CLI_OPTION_INSTALLATION_CREATE_DESKTOP_SHORTCUT_PROP = "salesforce.installation.shortcut.desktop"; + public static final String CLI_OPTION_INSTALLATION_FOLDER_PROP = "salesforce.installation.dir"; + public static final String CLI_OPTION_RUN_MODE = "run.mode"; + public static final String CLI_OPTION_CONFIG_DIR_PROP = "salesforce.config.dir"; + + // following command line options are set in UI mode when invoking java runtime + // for the 2nd iteration + public static final String CLI_OPTION_SYSTEM_PROXY_PORT = "sfdc.system.proxyPort"; + public static final String CLI_OPTION_SYSTEM_PROXY_HOST = "sfdc.system.proxyHost"; + public static final String CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH = "swt.nativelib.inpath"; + + // ======================= + // Configurable properties specified in config.properties file + // ======================= + // Loader Preferences + public static final String PROP_SAVE_ALL_PROPS = "salesforce.saveAllSettings"; + public static final String PROP_GMT_FOR_DATE_FIELD_VALUE = "datefield.usegmt"; + public static final String PROP_HIDE_WELCOME_SCREEN = "loader.hideWelcome"; + public static final String PROP_SHOW_LOADER_UPGRADE_SCREEN = "loader.ui.showUpgrade"; + + // Delimiter settings + public static final String PROP_CSV_DELIMITER_COMMA = "loader.csvComma"; + public static final String PROP_CSV_DELIMITER_TAB = "loader.csvTab"; + public static final String PROP_CSV_DELIMITER_OTHER = "loader.csvOther"; + public static final String PROP_CSV_DELIMITER_OTHER_VALUE = "loader.csvOtherValue"; + public static final String PROP_CSV_DELIMITER_FOR_QUERY_RESULTS = "loader.query.delimiter"; + public static final String PROP_BUFFER_UNPROCESSED_BULK_QUERY_RESULTS = "loader.bufferUnprocessedBulkQueryResults"; + public static final String PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS = "loader.query.includeBinaryData"; + public static final String PROP_CACHE_DESCRIBE_GLOBAL_RESULTS = "loader.cacheSObjectNamesAndFields"; + + //Special Internal Configs + public static final String PROP_SFDC_INTERNAL = "sfdcInternal"; //$NON-NLS-1$ + public static final String PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN = "sfdcInternal.isSessionIdLogin"; //$NON-NLS-1$ + public static final String PROP_SFDC_INTERNAL_SESSION_ID = "sfdcInternal.sessionId"; //$NON-NLS-1$ + + // salesforce client connectivity + public static final String PROP_USERNAME = "sfdc.username"; //$NON-NLS-1$ + public static final String PROP_PASSWORD = "sfdc.password"; //$NON-NLS-1$ + public static final String PROP_AUTH_ENDPOINT_LEGACY = "sfdc.endpoint"; + public static final String PROP_AUTH_ENDPOINT_PROD = "sfdc.endpoint." + SERVER_PROD_ENVIRONMENT_VAL; //$NON-NLS-1$ + public static final String PROP_AUTH_ENDPOINT_SANDBOX = "sfdc.endpoint." + SERVER_SB_ENVIRONMENT_VAL; //$NON-NLS-1$ + public static final String PROP_PROXY_HOST = "sfdc.proxyHost"; //$NON-NLS-1$ + public static final String PROP_PROXY_PORT = "sfdc.proxyPort"; //$NON-NLS-1$ + public static final String PROP_PROXY_USERNAME = "sfdc.proxyUsername"; //$NON-NLS-1$ + public static final String PROP_PROXY_PASSWORD = "sfdc.proxyPassword"; //$NON-NLS-1$ + public static final String PROP_PROXY_NTLM_DOMAIN = "sfdc.proxyNtlmDomain"; //$NON-NLS-1$ + public static final String PROP_TIMEOUT_SECS = "sfdc.timeoutSecs"; //$NON-NLS-1$ + public static final String PROP_CONNECTION_TIMEOUT_SECS = "sfdc.connectionTimeoutSecs"; //$NON-NLS-1$ + public static final String PROP_NO_COMPRESSION = "sfdc.noCompression"; //$NON-NLS-1$ + public static final String PROP_ENABLE_RETRIES = "sfdc.enableRetries"; //$NON-NLS-1$ + public static final String PROP_MAX_RETRIES = "sfdc.maxRetries"; //$NON-NLS-1$ + public static final String PROP_MIN_RETRY_SLEEP_SECS = "sfdc.minRetrySleepSecs"; //$NON-NLS-1$ + public static final String PROP_DEBUG_MESSAGES = "sfdc.debugMessages"; //$NON-NLS-1$ + public static final String PROP_DEBUG_MESSAGES_FILE = "sfdc.debugMessagesFile"; //$NON-NLS-1$ + public static final String PROP_RESET_URL_ON_LOGIN = "sfdc.resetUrlOnLogin"; //$NON-NLS-1$ + public static final String PROP_TRUNCATE_FIELDS = "sfdc.truncateFields";//$NON-NLS-1$ + public static final String PROP_FORMAT_PHONE_FIELDS = "sfdc.formatPhoneFields";//$NON-NLS-1$ + + public static final String PROP_BULK_API_ENABLED = "sfdc.useBulkApi"; + public static final String PROP_BULK_API_SERIAL_MODE = "sfdc.bulkApiSerialMode"; + public static final String PROP_BULK_API_CHECK_STATUS_INTERVAL = "sfdc.bulkApiCheckStatusInterval"; + public static final String PROP_BULK_API_ZIP_CONTENT = "sfdc.bulkApiZipContent"; + public static final String PROP_BULKV2_API_ENABLED = "sfdc.useBulkV2Api"; + public static final String PROP_UPDATE_WITH_EXTERNALID = "sfdc.updateWithExternalId"; + public static final String PROP_DELETE_WITH_EXTERNALID = "sfdc.deleteWithExternalId"; + + public static final String PROP_WIRE_OUTPUT = "sfdc.wireOutput"; + public static final String PROP_TIMEZONE = "sfdc.timezone"; + + public static final String OAUTH_PREFIX = "sfdc.oauth."; + public static final String BULK_LITERAL = "bulk"; + public static final String PARTNER_LITERAL = "partner"; + public static final String CLIENTSECRET_LITERAL = "clientsecret"; + public static final String CLIENTID_LITERAL = "clientid"; + public static final String REDIRECTURI_LITERAL = "redirecturi"; + public static final String BULK_CLIENTID_LITERAL = BULK_LITERAL + "." + CLIENTID_LITERAL; + public static final String PARTNER_CLIENTID_LITERAL = PARTNER_LITERAL + "." + CLIENTID_LITERAL; + + public static final String PROP_SERVER_ENVIRONMENTS = OAUTH_PREFIX + "environments"; + public static final String PROP_SELECTED_SERVER_ENVIRONMENT = OAUTH_PREFIX + "environment"; + public static final String PROP_OAUTH_ACCESSTOKEN = OAUTH_PREFIX + "accesstoken"; + public static final String PROP_OAUTH_REFRESHTOKEN = OAUTH_PREFIX + "refreshtoken"; + public static final String PROP_OAUTH_LOGIN_FROM_BROWSER = OAUTH_PREFIX + "loginfrombrowser"; + public static final String PROP_CLIENTID_PROD_PARTNER = OAUTH_PREFIX + + SERVER_PROD_ENVIRONMENT_VAL + + "." + PARTNER_CLIENTID_LITERAL; + public static final String PROP_CLIENTID_PROD_BULK = OAUTH_PREFIX + + SERVER_PROD_ENVIRONMENT_VAL + + "." + BULK_CLIENTID_LITERAL; + public static final String PROP_CLIENTID_SANDBOX_PARTNER = OAUTH_PREFIX + + SERVER_SB_ENVIRONMENT_VAL + + "." + PARTNER_CLIENTID_LITERAL; + public static final String PROP_CLIENTID_SANDBOX_BULK = OAUTH_PREFIX + + SERVER_SB_ENVIRONMENT_VAL + + "." + BULK_CLIENTID_LITERAL; + public static final String PROP_OAUTH_CLIENTSECRET_PROD = OAUTH_PREFIX + + SERVER_PROD_ENVIRONMENT_VAL + + "." + CLIENTSECRET_LITERAL; + public static final String PROP_OAUTH_CLIENTSECRET_SB = OAUTH_PREFIX + + SERVER_SB_ENVIRONMENT_VAL + + "." + CLIENTSECRET_LITERAL; + public static final String PROP_OAUTH_REDIRECT_URI_PROD = OAUTH_PREFIX + + SERVER_PROD_ENVIRONMENT_VAL + + "." + REDIRECTURI_LITERAL; + public static final String PROP_OAUTH_REDIRECT_URI_SB = OAUTH_PREFIX + + SERVER_SB_ENVIRONMENT_VAL + + "." + REDIRECTURI_LITERAL; + public static final String OAUTH_REDIRECT_URI_SUFFIX = "services/oauth2/success"; + public static final String PROP_REUSE_CLIENT_CONNECTION = "sfdc.reuseClientConnection"; + public static final String PROP_RICH_TEXT_FIELD_REGEX = "sfdx.richtext.regex"; + + // salesforce operation parameters + public static final String PROP_INSERT_NULLS = "sfdc.insertNulls"; //$NON-NLS-1$ + public static final String PROP_ENTITY = "sfdc.entity"; //$NON-NLS-1$ + public static final String PROP_IMPORT_BATCH_SIZE = "sfdc.loadBatchSize"; //$NON-NLS-1$ + public static final String PROP_ASSIGNMENT_RULE = "sfdc.assignmentRule"; //$NON-NLS-1$ + public static final String PROP_IDLOOKUP_FIELD = "sfdc.externalIdField"; //$NON-NLS-1$ + public static final String PROP_EXPORT_BATCH_SIZE = "sfdc.extractionRequestSize"; //$NON-NLS-1$ + public static final String PROP_EXTRACT_SOQL = "sfdc.extractionSOQL"; //$NON-NLS-1$ + public static final String PROP_SORT_EXTRACT_FIELDS = "sfdc.sortExtractionFields"; //$NON-NLS-1$ + public static final String PROP_EXTRACT_ALL_CAPS_HEADERS="sfdc.extraction.allCapsHeaders"; + public static final String PROP_EXTRACT_CSV_OUTPUT_BOM="sfdc.extraction.outputByteOrderMark"; + public static final String PROP_LOAD_PRESERVE_WHITESPACE_IN_RICH_TEXT = "sfdc.load.preserveWhitespaceInRichText"; + + // + // process configuration (action parameters) + // + // process.name is used to load the DynaBean from process-conf.xml file + // with the same id as the value of process.name property. + public static final String PROP_PROCESS_NAME = "process.name"; + public static final String PROP_OPERATION = "process.operation"; //$NON-NLS-1$ + public static final String PROP_MAPPING_FILE = "process.mappingFile"; //$NON-NLS-1$ + public static final String PROP_EURO_DATES = "process.useEuropeanDates"; //$NON-NLS-1$ + + // process configuration + public static final String PROP_OUTPUT_STATUS_DIR = "process.statusOutputDirectory"; //$NON-NLS-1$ + public static final String PROP_OUTPUT_SUCCESS = "process.outputSuccess"; //$NON-NLS-1$ + public static final String PROP_ENABLE_EXTRACT_STATUS_OUTPUT = "process.enableExtractStatusOutput"; //$NON-NLS-1$ + public static final String PROP_ENABLE_LAST_RUN_OUTPUT = "process.enableLastRunOutput"; //$NON-NLS-1$ + public static final String PROP_LAST_RUN_OUTPUT_DIR = "process.lastRunOutputDirectory"; //$NON-NLS-1$ + public static final String PROP_OUTPUT_ERROR = "process.outputError"; //$NON-NLS-1$ + public static final String PROP_LOAD_ROW_TO_START_AT = "process.loadRowToStartAt"; //$NON-NLS-1$ + public static final String PROP_INITIAL_LAST_RUN_DATE = "process.initialLastRunDate"; + public static final String PROP_ENCRYPTION_KEY_FILE = "process.encryptionKeyFile"; //$NON-NLS-1$ + public static final String PROP_PROCESS_THREAD_NAME = "process.thread.name"; + public static final String PROP_PROCESS_KEEP_ACCOUNT_TEAM = "process.keepAccountTeam"; + public static final String PROP_PROCESS_EXIT_WITH_ERROR_ON_FAILED_ROWS_BATCH_MODE = "process.batchMode.exitWithErrorOnFailedRows"; + + // data access configuration (e.g., for CSV file, database, etc). + public static final String PROP_DAO_TYPE = "dataAccess.type"; //$NON-NLS-1$ + public static final String PROP_DAO_NAME = "dataAccess.name"; //$NON-NLS-1$ + public static final String PROP_DAO_READ_BATCH_SIZE = "dataAccess.readBatchSize"; + public static final String PROP_DAO_WRITE_BATCH_SIZE = "dataAccess.writeBatchSize"; + public static final String PROP_DAO_SKIP_TOTAL_COUNT = "dataAccess.skipTotalCount"; + + /* + * TODO: when batching is introduced to the DataAccess, these parameters will become useful + * public static final String DAO_REQUEST_SIZE = "dataAccess.extractionRequestSize"; + * public static final String DAO_BATCH_SIZE = "dataAccess.batchSize"; + */ + public static final String PROP_READ_UTF8 = "dataAccess.readUTF8"; //$NON-NLS-1$ + public static final String PROP_WRITE_UTF8 = "dataAccess.writeUTF8"; //$NON-NLS-1$ + public static final String PROP_READ_CHARSET = "dataAccess.readCharset"; + public static final String PROP_WRITE_CHARSET = "dataAccess.writeCharset"; + + public static final String PROP_API_VERSION="salesforce.api.version"; + public static final String PROP_OAUTH_INSTANCE_URL="salesforce.oauth.instanceURL"; + public static final String PROP_USE_LEGACY_HTTP_GET="sfdc.useLegacyHttpGet"; + public static final String PROP_USE_SYSTEM_PROPS_FOR_HTTP_CLIENT="sfdc.useSysPropsForHttpClient"; + public static final String PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS = "loader.query.limitOutputToQueryFields"; + + /** + * =============== PILOT config properties ======== + * - These properties are used for the features in pilot phase. These features are + * not supported. Also, they may not be complete. + * + * - The property name is prefixed with "pilot". Remove the prefix and move the property + * declaration above this comment section when the feature is complete and supported. + * + * - DO NOT EXPOSE THEM THROUGH THE UI. + * =================================================== + */ + public static final String PILOT_PROPERTY_PREFIX = "pilot."; + + /* + public static final String ENABLE_BULK_QUERY_PK_CHUNKING = PILOT_PROPERTY_PREFIX + "sfdc.enableBulkQueryPKChunking"; + public static final String BULK_QUERY_PK_CHUNK_SIZE = PILOT_PROPERTY_PREFIX + "sfdc.bulkQueryPKChunkSize"; + public static final String BULK_QUERY_PK_CHUNK_START_ROW = PILOT_PROPERTY_PREFIX + "sfdc.bulkQueryChunkStartRow"; + */ + public static final String PROP_DUPLICATE_RULE_ALLOW_SAVE = PILOT_PROPERTY_PREFIX + "sfdc.duplicateRule.allowSave"; //$NON-NLS-1$ + public static final String PROP_DUPLICATE_RULE_INCLUDE_RECORD_DETAILS = PILOT_PROPERTY_PREFIX + "sfdc.duplicateRule.includeRecordDetails"; //$NON-NLS-1$ + public static final String PROP_DUPLICATE_RULE_RUN_AS_CURRENT_USER = PILOT_PROPERTY_PREFIX + "sfdc.duplicateRule.runAsCurrentUser"; //$NON-NLS-1$ +/* + public static final String PROP_DAO_READ_PREPROCESSOR_SCRIPT = PILOT_PROPERTY_PREFIX + "dataAccess.read.preProcessorScript"; + public static final String PROP_DAO_WRITE_POSTPROCESSOR_SCRIPT = PILOT_PROPERTY_PREFIX + "dataAccess.write.postProcessorScript"; +*/ + /* + * =============================== + * End of config properties + * =============================== + */ + + /** + * Indicates whether a value as been changed + */ + private boolean inMemoryPropValuesHaveChanged = false; + + /** + * The file name used by the load method to load a property file. This filename is + * used to save the properties file when save is called. + */ + private String filename; + /** + * The lastRun is for last run statistics file + */ + private LastRunProperties lastRunProperties; + /** + * encrypter is a utility used internally in the config for reading/writing + * encrypted values. Right now, the list of encrypted values is known to this class only. + */ + private final EncryptionAesUtil encrypter = new EncryptionAesUtil(); + + private final String configDir; + + /** + * dateFormatter will be used for getting dates in/out of the configuration + * file(s) + */ + public static final DateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + + /** + * The string representation used for true("true"). + */ + public static final String TRUE = "true"; //$NON-NLS-1$ + + /** + * The string representation used for false("false"). + */ + public static final String FALSE = "false"; //$NON-NLS-1$ + + /** + * communications with bulk api always use UTF8 + */ + public static final String BULK_API_ENCODING = StandardCharsets.UTF_8.name(); + public static final String CONFIG_FILE = "config.properties"; //$NON-NLS-1$ + public static final String DEFAULT_RICHTEXT_REGEX = "<(?=[a-zA-Z/])(\"[^\"]*\"|'[^']*'|[^'\">])*>"; + + + /* + * command line options. Not stored in config.properties file. + * ************ + * Option names MUST start with the prefix "CLI_OPTION_" + * ************ + */ + public static final String RUN_MODE_UI_VAL = "ui"; + public static final String RUN_MODE_BATCH_VAL = "batch"; + public static final String RUN_MODE_INSTALL_VAL = "install"; + public static final String RUN_MODE_ENCRYPT_VAL = "encrypt"; + + public static final String PROP_SAVE_BULK_SERVER_LOAD_AND_RAW_RESULTS_IN_CSV = "process.bulk.saveServerLoadAndRawResultsInCSV"; + public static final String PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO = "process.bulk.cacheDataFromDao"; + public static final String PROP_READ_ONLY_CONFIG_PROPERTIES = "config.properties.readonly"; + public static final String PROP_WIZARD_WIDTH = "sfdc.ui.wizard.width"; + public static final String PROP_WIZARD_HEIGHT = "sfdc.ui.wizard.height"; + public static final String PROP_WIZARD_X_OFFSET = "sfdc.ui.wizard.xoffset"; + public static final String PROP_WIZARD_Y_OFFSET = "sfdc.ui.wizard.yoffset"; + public static final String PROP_WIZARD_CLOSE_ON_FINISH = "sfdc.ui.wizard.closeOnFinish"; + public static final String PROP_WIZARD_POPULATE_RESULTS_FOLDER_WITH_PREVIOUS_OP_RESULTS_FOLDER = "sfdc.ui.wizard.finishStep.prepopulateWithPreviousOpResultsFolder"; + public static final String DIALOG_BOUNDS_PREFIX = "sfdc.ui.dialog."; + public static final String DIALOG_WIDTH_SUFFIX = ".width"; + public static final String DIALOG_HEIGHT_SUFFIX = ".height"; + public static final int DEFAULT_WIZARD_WIDTH = 600; + public static final int DEFAULT_WIZARD_HEIGHT = 600; + public static final int DEFAULT_WIZARD_X_OFFSET = 50; + public static final int DEFAULT_WIZARD_Y_OFFSET = 50; + public static final int DIALOG_X_OFFSET = 50; + public static final int DIALOG_Y_OFFSET = 50; + private static final String CONFIG_DIR_DEFAULT_VALUE = "configs"; + private static String configurationsDir = null; + + private static final String LAST_RUN_FILE_SUFFIX = "_lastRun.properties"; //$NON-NLS-1$ + // Following properties are read-only, i.e. they are not overridden during save() to config.properties + // - These properties are not set in Advanced Settings dialog. + // - Make sure to list all sensitive properties such as password because these properties are not saved. + private static final String[] READ_ONLY_PROPERTY_NAMES = { + PROP_PASSWORD, + PROP_IDLOOKUP_FIELD, + PROP_MAPPING_FILE, + PROP_EXTRACT_SOQL, + PROP_OUTPUT_SUCCESS, + PROP_OUTPUT_ERROR, + PROP_DAO_NAME, + PROP_DAO_TYPE, + PROP_ENTITY, + PROP_OPERATION, + PROP_DEBUG_MESSAGES, + PROP_DEBUG_MESSAGES_FILE, + PROP_WIRE_OUTPUT, + PROP_PROCESS_THREAD_NAME, + PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO, + PROP_PROCESS_EXIT_WITH_ERROR_ON_FAILED_ROWS_BATCH_MODE, + PROP_SAVE_BULK_SERVER_LOAD_AND_RAW_RESULTS_IN_CSV, + PROP_API_VERSION, + PROP_READ_CHARSET, + PROP_READ_ONLY_CONFIG_PROPERTIES, + PROP_RICH_TEXT_FIELD_REGEX, + /* + PROP_DAO_READ_PREPROCESSOR_SCRIPT, + PROP_DAO_WRITE_POSTPROCESSOR_SCRIPT, + */ + PROP_DELETE_WITH_EXTERNALID, + PROP_OAUTH_ACCESSTOKEN, + PROP_OAUTH_REFRESHTOKEN, + PROP_OAUTH_INSTANCE_URL, + PROP_OAUTH_REDIRECT_URI_PROD, + PROP_OAUTH_REDIRECT_URI_SB, + PROP_OAUTH_CLIENTSECRET_PROD, + PROP_OAUTH_CLIENTSECRET_SB, + PROP_RESET_URL_ON_LOGIN, + PROP_USE_LEGACY_HTTP_GET, + PROP_USE_SYSTEM_PROPS_FOR_HTTP_CLIENT, + PROP_AUTH_ENDPOINT_LEGACY, + PROP_DAO_WRITE_BATCH_SIZE, + PROP_BUFFER_UNPROCESSED_BULK_QUERY_RESULTS, + PROP_ENABLE_LAST_RUN_OUTPUT, + PROP_ENCRYPTION_KEY_FILE, + PROP_LAST_RUN_OUTPUT_DIR, + PROP_PROCESS_NAME, + PROP_BULK_API_CHECK_STATUS_INTERVAL, + PROP_CONNECTION_TIMEOUT_SECS, + PROP_ENABLE_RETRIES, + PROP_LOAD_PRESERVE_WHITESPACE_IN_RICH_TEXT, + PROP_MAX_RETRIES, + PROP_MIN_RETRY_SLEEP_SECS, + PROP_REUSE_CLIENT_CONNECTION, + CLI_OPTION_RUN_MODE, + AppConfig.CLI_OPTION_CONFIG_DIR_PROP, + AppConfig.PROP_GMT_FOR_DATE_FIELD_VALUE, + AppConfig.CLI_OPTION_INSTALLATION_CREATE_DESKTOP_SHORTCUT_PROP, + AppConfig.CLI_OPTION_INSTALLATION_CREATE_MACOS_APPS_FOLDER_SHORTCUT_PROP, + AppConfig.CLI_OPTION_INSTALLATION_CREATE_WINDOWS_START_MENU_SHORTCUT_PROP, + AppConfig.CLI_OPTION_INSTALLATION_FOLDER_PROP, + AppConfig.PROP_SAVE_ALL_PROPS, + AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH, + AppConfig.CLI_OPTION_SYSTEM_PROXY_HOST, + AppConfig.CLI_OPTION_SYSTEM_PROXY_PORT, + }; + + // Properties that are not published as their are either not saved or are read-only + // for edge use-cases. + private static final String[] UNPUBLISHED_PROPERTY_NAMES = { + PROP_SFDC_INTERNAL, + PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, + PROP_SFDC_INTERNAL_SESSION_ID, + PROP_OAUTH_ACCESSTOKEN, + PROP_OAUTH_REFRESHTOKEN, + PROP_OAUTH_INSTANCE_URL, + PROP_DELETE_WITH_EXTERNALID, + PROP_PROCESS_THREAD_NAME, + PROP_USE_LEGACY_HTTP_GET, + PROP_SERVER_ENVIRONMENTS, + PROP_SELECTED_SERVER_ENVIRONMENT, + PROP_DAO_SKIP_TOTAL_COUNT, + AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH, + AppConfig.CLI_OPTION_INSTALLATION_FOLDER_PROP, + AppConfig.CLI_OPTION_SYSTEM_PROXY_HOST, + AppConfig.CLI_OPTION_SYSTEM_PROXY_PORT, + }; + + private static final String[] ENCRYPTED_PROPERTY_NAMES = { + PROP_PASSWORD, + PROP_PROXY_PASSWORD, + PROP_OAUTH_ACCESSTOKEN, + PROP_OAUTH_REFRESHTOKEN, + PROP_OAUTH_CLIENTSECRET_PROD, + PROP_OAUTH_CLIENTSECRET_SB, + }; + + /** + * Creates an empty config that loads from and saves to the a file.

Use the methods + * load() and save() to load and store this preference store.

+ * + * @param filename the file name + * @see #load() + * @see #save() + */ + private AppConfig(String filename, Map commandLineOptionsMap) throws ConfigInitializationException, IOException { + loadedProperties = new LinkedProperties(); + this.filename = filename; + + File configFile = new File(this.filename); + this.configDir = configFile.getParentFile().getAbsolutePath(); + + // initialize with defaults + // + this.setDefaults(commandLineOptionsMap); + this.defaultProperties = new LinkedProperties(); + this.defaultProperties.putAll(this.loadedProperties); + + // load from config.properties file + this.load(); + + // load parameter overrides after loading from config.properties + // parameter overrides are from two places: + // 1. process-conf.properties for CLI mode + // 2. command line options for both CLI and UI modes + this.loadPropertyOverrides(commandLineOptionsMap); + saveAllProps = getBoolean(AppConfig.PROP_SAVE_ALL_PROPS); + + // last run gets initialized after loading config and overrides + // since config params are needed for initializing last run. + initializeLastRun(getLastRunPrefix()); + + // Properties initialization completed. Configure OAuth environment next + setServerEnvironment(getString(PROP_SELECTED_SERVER_ENVIRONMENT)); + ConfigPropertyMetadata.generateCSV(this); + } + + private String getLastRunPrefix() { + String lastRunFilePrefix = RUN_MODE_UI_VAL; + if (isBatchMode()) { + lastRunFilePrefix = getString(AppConfig.PROP_PROCESS_NAME); + if (lastRunFilePrefix == null || lastRunFilePrefix.isBlank()) { + lastRunFilePrefix = getString(AppConfig.PROP_ENTITY) + getString(AppConfig.PROP_OPERATION); + } + if (lastRunFilePrefix == null || lastRunFilePrefix.isBlank()) { + lastRunFilePrefix = RUN_MODE_BATCH_VAL; + } + } + return lastRunFilePrefix; + } + + private void initializeLastRun(String lastRunFileNamePrefix) { + if (lastRunFileNamePrefix == null || lastRunFileNamePrefix.isBlank()) { + lastRunFileNamePrefix = getString(AppConfig.CLI_OPTION_RUN_MODE); + } + String lastRunFileName = lastRunFileNamePrefix + LAST_RUN_FILE_SUFFIX; + String lastRunDir = getString(AppConfig.PROP_LAST_RUN_OUTPUT_DIR); + if (lastRunDir == null || lastRunDir.length() == 0) { + lastRunDir = this.configDir; + } + + this.lastRunProperties = new LastRunProperties(lastRunFileName, lastRunDir, getBoolean(AppConfig.PROP_ENABLE_LAST_RUN_OUTPUT)); + // Need to initialize last run date if it's present neither in config or override + lastRunProperties.setDefault(LastRunProperties.LAST_RUN_DATE, getString(PROP_INITIAL_LAST_RUN_DATE)); + + try { + this.lastRunProperties.load(); + } catch (IOException e) { + logger.warn(Messages.getFormattedString("LastRun.errorLoading", new String[]{ + this.lastRunProperties.getFullPath(), e.getMessage()}), e); + } + } + + private boolean useBulkApiByDefault() { + return false; + } + + /** + * This sets the current defaults. + */ + private void setDefaults(Map cliOptionsMap) { + setDefaultValue(PROP_HIDE_WELCOME_SCREEN, true); + setDefaultValue(PROP_SHOW_LOADER_UPGRADE_SCREEN, true); + + setDefaultValue(PROP_CSV_DELIMITER_COMMA, true); + setDefaultValue(PROP_CSV_DELIMITER_TAB, true); + setDefaultValue(PROP_CSV_DELIMITER_OTHER, false); + setDefaultValue(PROP_CSV_DELIMITER_OTHER_VALUE, ""); + setDefaultValue(PROP_CSV_DELIMITER_FOR_QUERY_RESULTS, AppUtil.COMMA); + + setDefaultValue(PROP_AUTH_ENDPOINT_PROD, DEFAULT_ENDPOINT_URL_PROD); + setDefaultValue(PROP_AUTH_ENDPOINT_SANDBOX, DEFAULT_ENDPOINT_URL_SANDBOX); + + setDefaultValue(PROP_IMPORT_BATCH_SIZE, useBulkApiByDefault() ? DEFAULT_NUM_ROWS_BULK_API_IMPORT_BATCH : DEFAULT_NUM_ROWS_LOAD_BATCH); + setDefaultValue(PROP_LOAD_ROW_TO_START_AT, 0); + setDefaultValue(PROP_TIMEOUT_SECS, DEFAULT_TIMEOUT_SECS); + setDefaultValue(PROP_CONNECTION_TIMEOUT_SECS, DEFAULT_CONNECTION_TIMEOUT_SECS); + setDefaultValue(PROP_ENABLE_RETRIES, true); + setDefaultValue(PROP_MAX_RETRIES, DEFAULT_MAX_RETRIES); + setDefaultValue(PROP_MIN_RETRY_SLEEP_SECS, DEFAULT_MIN_RETRY_SECS); + setDefaultValue(PROP_ASSIGNMENT_RULE, ""); //$NON-NLS-1$ + setDefaultValue(PROP_INSERT_NULLS, false); + setDefaultValue(PROP_ENABLE_EXTRACT_STATUS_OUTPUT, false); + setDefaultValue(PROP_ENABLE_LAST_RUN_OUTPUT, true); + setDefaultValue(PROP_RESET_URL_ON_LOGIN, true); + setDefaultValue(PROP_EXPORT_BATCH_SIZE, DEFAULT_EXPORT_BATCH_SIZE); + setDefaultValue(PROP_SORT_EXTRACT_FIELDS, true); + setDefaultValue(PROP_DAO_WRITE_BATCH_SIZE, DEFAULT_DAO_WRITE_BATCH_SIZE); + setDefaultValue(PROP_DAO_READ_BATCH_SIZE, DEFAULT_DAO_READ_BATCH_SIZE); + setDefaultValue(PROP_TRUNCATE_FIELDS, true); + setDefaultValue(PROP_FORMAT_PHONE_FIELDS, false); + // TODO: When we're ready, make Bulk API turned on by default. + setDefaultValue(PROP_BULK_API_ENABLED, useBulkApiByDefault()); + setDefaultValue(PROP_BULK_API_SERIAL_MODE, false); + setDefaultValue(PROP_BULK_API_ZIP_CONTENT, false); + setDefaultValue(PROP_BULK_API_CHECK_STATUS_INTERVAL, DEFAULT_BULK_API_CHECK_STATUS_INTERVAL); + setDefaultValue(PROP_WIRE_OUTPUT, false); + setDefaultValue(PROP_DEBUG_MESSAGES, false); + setDefaultValue(PROP_TIMEZONE, TimeZone.getDefault().getID()); + //sfdcInternal settings + setDefaultValue(PROP_SFDC_INTERNAL, false); + setDefaultValue(PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); + setDefaultValue(PROP_SFDC_INTERNAL_SESSION_ID, (String) null); + + //oauth settings + setDefaultValue(PROP_SELECTED_SERVER_ENVIRONMENT, SERVER_PROD_ENVIRONMENT_VAL); + setDefaultValue(PROP_SERVER_ENVIRONMENTS, SERVER_PROD_ENVIRONMENT_VAL + AppUtil.COMMA + SERVER_SB_ENVIRONMENT_VAL); + + /* sfdc.oauth...clientid = DataLoaderBulkUI | DataLoaderPartnerUI */ + setDefaultValue(PROP_CLIENTID_PROD_BULK, BULK_CLIENTID_VAL); + setDefaultValue(PROP_CLIENTID_PROD_PARTNER, PARTNER_CLIENTID_VAL); + + setDefaultValue(PROP_CLIENTID_SANDBOX_BULK, BULK_CLIENTID_VAL); + setDefaultValue(PROP_CLIENTID_SANDBOX_PARTNER, PARTNER_CLIENTID_VAL); + + setDefaultValue(PROP_OPERATION, "insert"); + setDefaultValue(PROP_REUSE_CLIENT_CONNECTION, true); + /* + setDefaultValue(ENABLE_BULK_QUERY_PK_CHUNKING, false); + setDefaultValue(BULK_QUERY_PK_CHUNK_SIZE, DEFAULT_BULK_QUERY_PK_CHUNK_SIZE); + setDefaultValue(BULK_QUERY_PK_CHUNK_START_ROW, ""); + */ + setDefaultValue(PROP_DUPLICATE_RULE_ALLOW_SAVE, false); + setDefaultValue(PROP_DUPLICATE_RULE_INCLUDE_RECORD_DETAILS, false); + setDefaultValue(PROP_DUPLICATE_RULE_RUN_AS_CURRENT_USER, false); + setDefaultValue(PROP_BUFFER_UNPROCESSED_BULK_QUERY_RESULTS, false); + setDefaultValue(PROP_BULKV2_API_ENABLED, false); + setDefaultValue(PROP_UPDATE_WITH_EXTERNALID, false); + setDefaultValue(PROP_DELETE_WITH_EXTERNALID, false); + setDefaultValue(PROP_OAUTH_LOGIN_FROM_BROWSER, true); + setDefaultValue(PROP_LOAD_PRESERVE_WHITESPACE_IN_RICH_TEXT, true); + setDefaultValue(CLI_OPTION_RUN_MODE, RUN_MODE_UI_VAL); + setDefaultValue(PROP_SAVE_BULK_SERVER_LOAD_AND_RAW_RESULTS_IN_CSV, false); + setDefaultValue(PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO, true); + setDefaultValue(PROP_PROCESS_KEEP_ACCOUNT_TEAM, false); + setDefaultValue(PROP_WIZARD_WIDTH, DEFAULT_WIZARD_WIDTH); + setDefaultValue(PROP_WIZARD_HEIGHT, DEFAULT_WIZARD_HEIGHT); + /* + setDefaultValue(PROP_DAO_READ_PREPROCESSOR_SCRIPT, ""); + setDefaultValue(PROP_DAO_WRITE_POSTPROCESSOR_SCRIPT, ""); + */ + setDefaultValue(PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS, true); + setDefaultValue(PROP_WIZARD_CLOSE_ON_FINISH, true); + setDefaultValue(PROP_WIZARD_POPULATE_RESULTS_FOLDER_WITH_PREVIOUS_OP_RESULTS_FOLDER, true); + setDefaultValue(PROP_WIZARD_X_OFFSET, DEFAULT_WIZARD_X_OFFSET); + setDefaultValue(PROP_WIZARD_Y_OFFSET, DEFAULT_WIZARD_Y_OFFSET); + setDefaultValue(PROP_CACHE_DESCRIBE_GLOBAL_RESULTS, true); + setDefaultValue(PROP_PROCESS_EXIT_WITH_ERROR_ON_FAILED_ROWS_BATCH_MODE, false); + setDefaultValue(PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS, false); + setDefaultValue(PROP_OAUTH_INSTANCE_URL, false); + String systemProxyHost = cliOptionsMap.get(AppConfig.CLI_OPTION_SYSTEM_PROXY_HOST); + String systemProxyPort = cliOptionsMap.get(AppConfig.CLI_OPTION_SYSTEM_PROXY_PORT); + if (systemProxyHost != null && !systemProxyHost.isBlank()) { + setDefaultValue(PROP_PROXY_HOST, systemProxyHost); + setDefaultValue(PROP_PROXY_PORT, systemProxyPort); + } + setDefaultValue(PROP_USE_LEGACY_HTTP_GET, false); + setDefaultValue(PROP_USE_SYSTEM_PROPS_FOR_HTTP_CLIENT, true); + setDefaultValue(PROP_EURO_DATES, false); + setDefaultValue(PROP_NO_COMPRESSION, false); + setDefaultValue(PROP_READ_UTF8, false); + setDefaultValue(PROP_WRITE_UTF8, false); + setDefaultValue(PROP_RICH_TEXT_FIELD_REGEX, DEFAULT_RICHTEXT_REGEX); + setDefaultValue(PROP_DAO_SKIP_TOTAL_COUNT, true); + setDefaultValue(PROP_READ_CHARSET ,getDefaultCharsetForCsvReadWrite()); + setDefaultValue(PROP_WRITE_CHARSET ,getDefaultCharsetForCsvReadWrite()); + setDefaultValue(PROP_GMT_FOR_DATE_FIELD_VALUE, false); + setDefaultValue(PROP_SAVE_ALL_PROPS, false); + setDefaultValue(PROP_EXTRACT_ALL_CAPS_HEADERS, false); + setDefaultValue(PROP_EXTRACT_CSV_OUTPUT_BOM, true); + + } + + /** + * Returns true if the name is a key in the config + * + * @param name the name of the key + */ + public boolean contains(String name) { + return loadedProperties.containsKey(name) || lastRunProperties.hasParameter(name) && lastRunProperties.containsKey(name); + } + + /** + * Gets boolean for a given name + * + * @return boolean + */ + public boolean getBoolean(String name) { + String value = getParamValue(name); + if (value == null || value.length() == 0) return BOOLEAN_DEFAULT; + if (value.equals(AppConfig.TRUE)) return true; + return false; + } + + /** + * Gets double for a given name. + * + * @return double + */ + + public double getDouble(String name) throws ParameterLoadException { + String value = getParamValue(name); + if (value == null || value.length() == 0) return DOUBLE_DEFAULT; + double ival; + try { + ival = Double.parseDouble(value); + } catch (NumberFormatException e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, + Double.class.getName()}); + logger.warn(errMsg, e); + throw new ParameterLoadException(e.getMessage(), e); + } + return ival; + } + + /** + * Gets float for a given name. + * + * @return float + */ + public float getFloat(String name) throws ParameterLoadException { + String value = getParamValue(name); + if (value == null || value.length() == 0) return FLOAT_DEFAULT; + float ival = FLOAT_DEFAULT; + try { + ival = Float.parseFloat(value); + } catch (NumberFormatException e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, + Float.class.getName()}); + logger.warn(errMsg, e); + throw new ParameterLoadException(e.getMessage(), e); + } + return ival; + } + + /** + * Gets int for a given name. + * + * @return int + */ + public int getInt(String name) throws ParameterLoadException { + String value = getParamValue(name); + if (value == null || value.length() == 0) return INT_DEFAULT; + int ival = 0; + try { + ival = Integer.parseInt(value); + } catch (NumberFormatException e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, + Integer.class.getName()}); + logger.warn(errMsg, e); + throw new ParameterLoadException(e.getMessage(), e); + } + return ival; + } + + /** + * Gets long for a given name. + * + * @return long + */ + public long getLong(String name) throws ParameterLoadException { + String value = getParamValue(name); + if (value == null || value.length() == 0) return LONG_DEFAULT; + long ival = LONG_DEFAULT; + try { + ival = Long.parseLong(value); + } catch (NumberFormatException e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, + Long.class.getName()}); + logger.warn(errMsg, e); + throw new ParameterLoadException(e.getMessage(), e); + } + return ival; + } + + /** + * Gets required string param for a given name. If not found, throws an exception + * + * @return string + */ + public String getStringRequired(String name) throws ParameterLoadException { + String value = getString(name); + if (value == null || value.length() == 0) { + String errMsg = Messages.getFormattedString("Config.errorNoRequiredParameter", name); //$NON-NLS-1$ + logger.fatal(errMsg); + throw new ParameterLoadException(errMsg); + } + return value; + } + + /** + * Gets string for a given name. + * + * @return string + */ + public String getString(String name) { + String value = getParamValue(name); + if (value == null) return STRING_DEFAULT; + return value; + } + + + public ArrayList getStrings(String name) { + String values = getString(name); + ArrayList list = new ArrayList<>(); + if (values != null && !values.trim().isEmpty()) { + Collections.addAll(list, values.trim().split(AppUtil.COMMA)); + } + return list; + } + + /** + * Gets an enum value for a given config name. + */ + public > T getEnum(Class enumClass, String name) { + return Enum.valueOf(enumClass, getString(name)); + } + + public TimeZone getTimeZone() { + return TimeZone.getTimeZone(getString(PROP_TIMEZONE)); + } + + /** + * Gets path to a config file given the config file property name + * + * @param configFileProperty property containing a config filename + * @return Config filename path based on config property value. Config file is assumed to reside + * in the global config folder + */ + public String getConfigFilename(String configFileProperty) { + String value = getParamValue(configFileProperty); + if (value == null) return null; + return constructConfigFilePath(new File(value).getName()); + } + + public String getLastRunFilename() { + return this.lastRunProperties == null ? null : this.lastRunProperties.getFullPath(); + } + + + /** + * Constructs config file path based on the configuration folder and the passed in config + * filename + * + * @param configFilename Config filename that resides in the config folder + * @return Full path to the config file + */ + public String constructConfigFilePath(String configFilename) { + File configPathFile = new File(this.filename).getParentFile(); + return new File(configPathFile, configFilename).getAbsolutePath(); + } + + /** + * @return Date + */ + public Date getDate(String name) throws ParameterLoadException { + String value = getParamValue(name); + if (value == null || value.length() == 0) return Calendar.getInstance().getTime(); + Date dval = null; + try { + dval = DATE_FORMATTER.parse(value); + } catch (ParseException e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, + Date.class.getName()}); + logger.warn(errMsg, e); + throw new ParameterLoadException(e.getMessage(), e); + } + return dval; + } + + /** + * Get map from a string param value. String format of map is key1=val1,key2=val2,... + * + * @return Map + */ + public Map getMap(String name) throws ParameterLoadException { + String value = getParamValue(name); + if (value == null || value.length() == 0) return MAP_STRING_DEFAULT; + Map mval = new HashMap(); + String[] pairs = value.split(AppUtil.COMMA); + for (String pair : pairs) { + String[] nameValue = pair.split("="); + if (nameValue.length != 2) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, + Map.class.getName()}); + logger.warn(errMsg); + throw new ParameterLoadException(errMsg); + } + mval.put(nameValue[0], nameValue[1]); + } + return mval; + } + + /** + * @return parameter value + */ + + private String getParamValue(String name) { + String propValue; + + if (lastRunProperties != null && lastRunProperties.hasParameter(name)) { + propValue = lastRunProperties.getProperty(name); + } else { + propValue = loadedProperties != null ? loadedProperties.getProperty(name) : null; + } + + // check if a property's value is configured when it used to be a pilot property + // if value not set. + if (propValue == null && !name.startsWith(PILOT_PROPERTY_PREFIX)) { + String pilotName = PILOT_PROPERTY_PREFIX + name; + String pilotValue = getParamValue(pilotName); + if (pilotValue != null && !pilotValue.isEmpty()) { + // if picking up the value from a pilot property that is no longer in pilot, + // set the value for the new property to be the same as the value of the pilot property + doSetPropertyAndUpdateConfig(name, propValue, pilotValue, false); + } + propValue = pilotValue; + } + + return propValue; + } + + /** + * Prints the contents of this preference store to the given print stream. + * + * @param out the print stream + */ + public void list(PrintStream out) { + loadedProperties.list(out); + lastRunProperties.list(out); + } + + /** + * Prints the contents of this preference store to the given print writer. + * + * @param out the print writer + */ + public void list(PrintWriter out) { + loadedProperties.list(out); + lastRunProperties.list(out); + } + + /** + * Loads this preference store from the file established in the constructor + * Config(java.lang.String) (or by setFileName). Default preference + * values are not affected. + * + * @throws java.io.IOException if there is a problem loading this store + */ + public void load() throws IOException, ConfigInitializationException { + if (filename == null) { + logger.fatal(Messages.getString("Config.fileMissing")); + throw new IOException(Messages.getString("Config.fileMissing")); //$NON-NLS-1$ + } + FileInputStream in = new FileInputStream(filename); + try { + load(in); + } finally { + in.close(); + } + } + + /** + * Loads this preference store from the given input stream. Default preference values are not + * affected. + * + * @param in the input stream + * @throws ConfigInitializationException If there's a problem loading the parameters + * @throws IOException IF there's an I/O problem loading parameter from file + */ + private void load(InputStream in) throws ConfigInitializationException, IOException { + try { + Properties propsFromFile = new LinkedProperties(); + propsFromFile.load(in); + removeEmptyProperties(propsFromFile); + loadedProperties.putAll(propsFromFile); + for (String roprop : READ_ONLY_PROPERTY_NAMES) { + if (propsFromFile.containsKey(roprop)) { + this.readOnlyPropertiesFromPropertiesFile.put( + roprop, + propsFromFile.get(roprop)); + } + + } + } catch (IOException e) { + logger.fatal(Messages.getFormattedString("Config.errorPropertiesLoad", e.getMessage())); + throw e; + } + // paramter post-processing + postLoad(loadedProperties, true); + + inMemoryPropValuesHaveChanged = false; + } + + public static boolean isReadOnlyProperty(String propertyName) { + if (propertyName == null) { + return false; + } + for (String roProp : AppConfig.READ_ONLY_PROPERTY_NAMES) { + if (roProp.equals(propertyName)) { + return true; + } + } + return false; + } + + private boolean isOverrideProperty(String propertyName) { + if (propertyName == null) { + return false; + } + if (this.overridesMap != null + && this.overridesMap.containsKey(propertyName)) { + return true; + } + return false; + } + + public static boolean isInternalProperty(String propertyName) { + if (propertyName == null) { + return false; + } + for (String roProp : AppConfig.UNPUBLISHED_PROPERTY_NAMES) { + if (roProp.equals(propertyName)) { + return true; + } + } + return false; + } + public static boolean isEncryptedProperty(String propertyName) { + if (propertyName == null) { + return false; + } + for (String encryptedProp : AppConfig.ENCRYPTED_PROPERTY_NAMES) { + if (encryptedProp.equals(propertyName)) { + return true; + } + } + return false; + } + /** + * Post process parameters. Right now, only decrypts encrypted values in the map + * + * @param values Values to be post-processed + */ + @SuppressWarnings("unchecked") + private void postLoad(Map propMap, boolean isConfigFilePropsMap) throws ConfigInitializationException { + // initialize encryption + initEncryption((Map) propMap); + + // decrypt encrypted values + for (String encryptedProp : ENCRYPTED_PROPERTY_NAMES) { + decryptAndCacheProperty(propMap, encryptedProp); + } + + // Do not load unsupported properties and CLI options even if they are specified in config.properties file + if (isConfigFilePropsMap) { + removeCLIOnlyOptionsFromProperties(); + removeUnsupportedProperties(); + } + } + + private void decryptAndCacheProperty(Map values, String propertyName) throws ConfigInitializationException { + @SuppressWarnings("unchecked") + Map propMap = (Map)values; + // initialize encryption + if (propMap != null && propMap.containsKey(propertyName)) { + if (propMap.containsKey(propertyName + DECRYPTED_SUFFIX)) { + String decryptedPropValue = propMap.get(propertyName + DECRYPTED_SUFFIX); + String propValueToBeDecrypted = propMap.get(propertyName); + if (decryptedPropValue != null + && propValueToBeDecrypted != null + && decryptedPropValue.equals(propValueToBeDecrypted)) { + return; // do not decrypt an already decrypted value + } + } + String propValue = decryptProperty(encrypter, propMap, propertyName, isBatchMode()); + if (propValue == null) propValue = ""; + propMap.put(propertyName, propValue); + + // cache decrypted value + propMap.put(propertyName + DECRYPTED_SUFFIX, propValue); + } + } + + /** + * Load config parameter override values. The main use case is loading of overrides from + * external config file + */ + public void loadPropertyOverrides(Map overridesMap) throws ParameterLoadException, + ConfigInitializationException { + this.overridesMap = overridesMap; + // make sure to post-process the args to be loaded + postLoad(overridesMap, false); + + // replace values in the Config + putValues(overridesMap); + } + + /** + * Decrypt property with propName using the encrypter. If decryption succeeds, return the + * decrypted value + * + * @return decrypted property value + */ + static private String decryptProperty(EncryptionAesUtil encrypter, Map propMap, String propName, boolean isBatch) + throws ParameterLoadException { + String propValue = propMap.get(propName); + if (propValue != null && propValue.length() > 0) { + try { + return encrypter.decryptMsg(propValue); + } catch (GeneralSecurityException e) { + // if running in the UI, we can ignore encryption errors + if (isBatch) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{propName, + String.class.getName()}); + logger.error(errMsg, e); + throw new ParameterLoadException(errMsg, e); + } else { + return null; + } + } catch (Exception e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{propName, + String.class.getName()}); + logger.error(errMsg, e); + throw new ParameterLoadException(errMsg, e); + } + } + return propValue; + } + + /** + * Decrypt property with propName using the encrypter. If decryption succeeds, return the + * decrypted value + * + * @return decrypted property value + */ + static private String encryptProperty(EncryptionAesUtil encrypter, Map propMap, String propName, boolean isBatch) + throws ParameterLoadException { + String propValue = propMap.get(propName); + if (propValue != null && propValue.length() > 0) { + try { + return encrypter.encryptMsg(propValue); + } catch (GeneralSecurityException e) { + // if running in the UI, we can ignore encryption errors + if (isBatch) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{propName, + String.class.getName()}); + logger.error(errMsg, e); + throw new ParameterLoadException(errMsg, e); + } else { + return null; + } + } catch (Exception e) { + String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{propName, + String.class.getName()}); + logger.error(errMsg, e); + throw new ParameterLoadException(errMsg, e); + } + } + return propValue; + } + /** + * @throws ConfigInitializationException + */ + private void initEncryption(Map values) throws ConfigInitializationException { + if (values == null) { + return; + } + // initialize encrypter + String keyFile = values.get(PROP_ENCRYPTION_KEY_FILE); + if (keyFile != null && keyFile.length() != 0) { + try { + encrypter.setCipherKeyFromFilePath(keyFile); + } catch (Exception e) { + String errMsg = Messages.getFormattedString("Config.errorSecurityInit", new String[]{keyFile, + e.getMessage()}); + logger.error(errMsg); + throw new ConfigInitializationException(errMsg); + } + } + + } + + /** + * Puts a set of values from a map into config + * + * @param values Map of overriding values + */ + public void putValues(Map values) throws ParameterLoadException, ConfigInitializationException { + if (values == null) { + return; + } + for (String key : values.keySet()) { + setProperty(key, values.get(key), false); + } + } + + /** + * Saves the preferences to the file from which they were originally loaded. + * + * @throws java.io.IOException if there is a problem saving this store + */ + public void save() throws IOException, GeneralSecurityException { + // lastrun properties are always saved + lastRunProperties.save(); + + if (getString(AppConfig.CLI_OPTION_RUN_MODE).equalsIgnoreCase(AppConfig.RUN_MODE_BATCH_VAL) + || getBoolean(PROP_READ_ONLY_CONFIG_PROPERTIES) + || !inMemoryPropValuesHaveChanged) { + return; // do not save any updates to config.properties file + } + if (filename == null) { + throw new IOException(Messages.getString("Config.fileMissing")); //$NON-NLS-1$ + } + + Properties inMemoryProperties = new LinkedProperties(); + inMemoryProperties.putAll(this.loadedProperties); + + // property additions section - all property additions occur before removals + // add encrypted property name, value pairs for saving + encryptPropertiesBeforeSave(); + + // property removals section - all property additions occur before this + removeCommandLineOptionsBeforeSave(); + removeReadOnlyPropertiesBeforeSave(); + removeUnsupportedProperties(); + removeDecryptedPropertiesBeforeSave(); + removeCLIOnlyOptionsFromProperties(); + removeEmptyProperties(this.loadedProperties); + removeDefaultPropertiesBeforeSave(); + + FileOutputStream out = null; + try { + out = new FileOutputStream(filename); + saveConfigProperties(out, "Salesforce Data Loader Config", loadedProperties); //$NON-NLS-1$ + } finally { + if (out != null) { + out.close(); + } + // restore original in-memory property values + loadedProperties = inMemoryProperties; + } + } + + public void setAuthEndpointForCurrentEnv(String authEndpoint) { + this.setAuthEndpointForEnv(authEndpoint, getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT)); + } + + public void setAuthEndpointForEnv(String authEndpoint, String env) { + AppUtil.validateAuthenticationHostDomainUrlAndThrow(authEndpoint); + if (env != null && env.equalsIgnoreCase(AppConfig.SERVER_SB_ENVIRONMENT_VAL)) { + this.setValue(AppConfig.PROP_AUTH_ENDPOINT_SANDBOX, authEndpoint); + } else { + this.setValue(AppConfig.PROP_AUTH_ENDPOINT_PROD, authEndpoint); + } + } + + public String getAuthEndpointForCurrentEnv() { + String endpoint = null; + if (AppConfig.SERVER_SB_ENVIRONMENT_VAL.equals(this.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT))) { + endpoint = getString(AppConfig.PROP_AUTH_ENDPOINT_SANDBOX); + } else { + endpoint = getString(AppConfig.PROP_AUTH_ENDPOINT_PROD); + } + + // try with legacy endpoint property + if (endpoint == null || endpoint.isBlank()) { + endpoint = getString(PROP_AUTH_ENDPOINT_LEGACY); + } + if (endpoint == null || endpoint.isBlank()) { + endpoint = getDefaultAuthEndpointForCurrentEnv(); + } + if (!endpoint.endsWith("/")) { + endpoint += "/"; + } + AppUtil.validateAuthenticationHostDomainUrlAndThrow(endpoint); + return endpoint; + } + + public String getDefaultAuthEndpointForCurrentEnv() { + if (AppConfig.SERVER_SB_ENVIRONMENT_VAL.equals(this.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT))) { + return AppConfig.DEFAULT_ENDPOINT_URL_SANDBOX; + } else { // assume production is the only alternate environment + return AppConfig.DEFAULT_ENDPOINT_URL_PROD; + } + } + + public boolean isDefaultAuthEndpointForCurrentEnv(String endpoint) { + if (endpoint == null || endpoint.isBlank()) { + return false; + } + if (AppConfig.SERVER_SB_ENVIRONMENT_VAL.equals(this.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT))) { + return AppConfig.DEFAULT_ENDPOINT_URL_SANDBOX.equalsIgnoreCase(endpoint); + } else { // assume production is the only alternate environment + return AppConfig.DEFAULT_ENDPOINT_URL_PROD.equalsIgnoreCase(endpoint); + } + } + + private void encryptPropertiesBeforeSave() { + for (String encryptedProp : ENCRYPTED_PROPERTY_NAMES) { + if (this.loadedProperties.containsKey(encryptedProp)) { + Map propMap = (Map)this.loadedProperties; + try { + @SuppressWarnings("unchecked") + String propValue = encryptProperty(encrypter, + (Map)propMap, + encryptedProp, isBatchMode()); + this.loadedProperties.put(encryptedProp, propValue); + } catch (ParameterLoadException e) { + this.loadedProperties.remove(encryptedProp); // Encryption attempt failed. Do not save. + } + } + } + } + + private void removeCommandLineOptionsBeforeSave() { + if (this.overridesMap != null) { + for (String propertyName : this.overridesMap.keySet()) { + this.loadedProperties.remove(propertyName); + } + } + } + + private void removeReadOnlyPropertiesBeforeSave() { + for (String roprop : READ_ONLY_PROPERTY_NAMES) { + if (!this.readOnlyPropertiesFromPropertiesFile.containsKey(roprop)) { + this.loadedProperties.remove(roprop); + } + } + } + + private void removeUnsupportedProperties() { + // do not save a value for enabling Bulk V2 + //this.properties.remove(BULKV2_API_ENABLED); + } + + private void removeDefaultPropertiesBeforeSave() { + if (saveAllProps) { + // do not remove default values + return; + } + this.loadedProperties.entrySet().removeIf( + entry -> (this.defaultProperties.get(entry.getKey().toString()) != null + && this.defaultProperties.get(entry.getKey().toString()).equals(entry.getValue()))); + } + + private void removeDecryptedPropertiesBeforeSave() { + this.loadedProperties.entrySet().removeIf(entry -> (entry.getKey().toString().endsWith(DECRYPTED_SUFFIX))); + } + + private void removeCLIOnlyOptionsFromProperties() { + Set keys = this.loadedProperties.stringPropertyNames(); + Field[] allFields = AppConfig.class.getDeclaredFields(); + for (Field field : allFields) { + if (field.getName().startsWith("CLI_OPTION_")) { + String fieldVal = null; + try { + fieldVal = (String)field.get(null); + } catch (IllegalArgumentException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + if (fieldVal == null) { + continue; + } + for (String key : keys) { + if (key.equalsIgnoreCase(fieldVal)) { + this.loadedProperties.remove(key); + } + } + } + } + } + + private void removeEmptyProperties(Properties props) { + props.entrySet().removeIf(entry -> + (entry.getValue() == null || entry.getValue().toString().isBlank())); + } + + /** + * Save statistics from the last run + */ + public void saveLastRun() throws IOException { + lastRunProperties.save(); + } + + /** + * Saves this config to the given output stream. The given string is inserted as header + * information. + * + * @param out the output stream + * @param header the header + * @throws java.io.IOException if there is a problem saving this store + */ + private void saveConfigProperties(OutputStream out, String header, Properties configProperties) throws IOException { + if (getString(AppConfig.CLI_OPTION_RUN_MODE).equalsIgnoreCase(AppConfig.RUN_MODE_BATCH_VAL) + || getBoolean(PROP_READ_ONLY_CONFIG_PROPERTIES) + || !inMemoryPropValuesHaveChanged) { + return; + } + configProperties.store(out, header); + inMemoryPropValuesHaveChanged = false; + } + + public void setValue(String name, Map valueMap) { + StringBuilder sb = new StringBuilder(); + for (String key : valueMap.keySet()) { + // add comma for subsequent entries + if (sb.length() != 0) { + sb.append(AppUtil.COMMA); + } + sb.append(key + "=" + valueMap.get(key)); + } + setProperty(name, sb.toString(), false); + } + + public static final String CLIENT_ID_HEADER_NAME="client_id"; + + public String getClientIdNameValuePair() { + return CLIENT_ID_HEADER_NAME + "=" + this.getClientIDForCurrentEnv(); + } + + /** + * Sets the name of the file used when loading and storing this config + * + * @param name the file name + * @see #load() + * @see #save() + */ + public void setFilename(String name) { + filename = name; + } + + /** + * @return the current configuration filename + */ + public String getFilename() { + return filename; + } + + /* + * Sets a date + */ + public void setValue(String name, Date value) { + setValue(name, value, false); + } + + private void setValue(String name, Date value, boolean skipIfAlreadySet) { + setProperty(name, DATE_FORMATTER.format(value), skipIfAlreadySet); + } + + /** + * Sets a list of String values + */ + public void setValue(String name, String... values) { + setValue(name, false, values); + } + + private void setValue(String name, boolean skipIfAlreadySet, String... values) { + if (values != null && values.length > 1) { + StringJoiner joiner = new StringJoiner(AppUtil.COMMA); + for (String value : values) { + joiner.add(value); + } + setProperty(name, joiner.toString(), skipIfAlreadySet); + } else if (values != null && values.length > 0) { + setProperty(name, values[0], skipIfAlreadySet); + } else { + setProperty(name, null, skipIfAlreadySet); + } + + } + + /** + * Sets a value other than a date or a list of String values + */ + public void setValue(String name, T value) { + setValue(name, value, false); + } + + + private void setDefaultValue(String name, T value) { + setValue(name, value, true); + } + + private void setValue(String name, T value, boolean skipIfAlreadySet) { + if (value == null) { + return; + } + String oldValue = getString(name); + if (oldValue == null || !oldValue.equals(value)) { + setProperty(name, value.toString(), skipIfAlreadySet); + } + } + + private void setConfigPropertyMetadata(String propName, String propVal, boolean isDefault) { + ConfigPropertyMetadata configPropMD = configPropsMetadataMap.get(propName); + if (configPropMD == null) { + return; // did not find the property metadata in the registry of properties + } + if (isDefault) { + configPropMD.setDefaultValue(propVal); + } + } + + /** + * @param name + * @param newValue + */ + private void setProperty(String name, String newValue, boolean skipIfAlreadySet) { + setConfigPropertyMetadata(name, newValue, skipIfAlreadySet); + final String oldValue = getString(name); + if (skipIfAlreadySet && oldValue != null && !oldValue.isBlank()) { + // do not override the old value + return; + } + final boolean paramChanged = (oldValue == null || oldValue.length() == 0) ? (newValue != null && newValue + .length() > 0) : !oldValue.equals(newValue); + if (paramChanged) { + doSetPropertyAndUpdateConfig(name, oldValue, newValue, skipIfAlreadySet); + } + } + + private void doSetPropertyAndUpdateConfig(String name, String oldValue, String newValue, boolean skipIfAlreadySet) { + configChanged(name, oldValue, newValue); + if (oldValue == null) { + oldValue = ""; + } + if (newValue == null) { + newValue = ""; + } + if (lastRunProperties != null && lastRunProperties.hasParameter(name)) { + lastRunProperties.put(name, newValue); + } else { + loadedProperties.put(name, newValue); + } + if (!isReadOnlyProperty(name) + && !isOverrideProperty(name) + && !isInternalProperty(name) + && !skipIfAlreadySet + && !lastRunProperties.hasParameter(name)) { + //read-only properties, internal properties, + // or properties overridden by setting them in process-conf.xml + // or passing them as command line options should not result in updates + // to config.properties file. + // + // Also, if skipIfAlreadySet==true, the property was not set + // in config.properties file. It is being set to its default value. + this.inMemoryPropValuesHaveChanged = true; + } + } + + public boolean isBatchMode() { + return (AppUtil.getAppRunMode() == AppUtil.APP_RUN_MODE.BATCH); + } + + public int getMaxRowsInImportBatch() { + boolean bulkApi = isBulkAPIEnabled(); + boolean bulkV2Api = this.isBulkV2APIEnabled(); + + if (bulkV2Api) { + return MAX_NUM_ROWS_BULKV2_API_IMPORT_JOB; + } + + int bs = -1; + try { + bs = getInt(PROP_IMPORT_BATCH_SIZE); + } catch (ParameterLoadException e) { + } + int maxBatchSize = bulkApi ? MAX_NUM_ROWS_BULK_API_IMPORT_BATCH : MAX_NUM_ROWS_SOAP_API_IMPORT_BATCH; + return bs > maxBatchSize ? maxBatchSize : bs > 0 ? bs : getDefaultImportBatchSize(bulkApi, bulkV2Api); + } + + public int getDefaultImportBatchSize(boolean bulkApi, boolean bulkV2Api) { + if (bulkV2Api) { + return MAX_NUM_ROWS_BULKV2_API_IMPORT_JOB; + } + return bulkApi ? DEFAULT_NUM_ROWS_BULK_API_IMPORT_BATCH : DEFAULT_NUM_ROWS_LOAD_BATCH; + } + + public int getMaxImportBatchSize(boolean bulkApi, boolean bulkV2Api) { + if (bulkV2Api) { + return MAX_NUM_ROWS_BULKV2_API_IMPORT_JOB; + } + return bulkApi ? MAX_NUM_ROWS_BULK_API_IMPORT_BATCH : MAX_NUM_ROWS_SOAP_API_IMPORT_BATCH; + } + + public boolean useBulkAPIForCurrentOperation() { + return (isBulkAPIEnabled() || isBulkV2APIEnabled()) && isBulkApiOperation(); + } + + public boolean isBulkAPIEnabled() { + return getBoolean(PROP_BULK_API_ENABLED) && !isBulkV2APIEnabled(); + } + + public boolean isBulkV2APIEnabled() { + return getBoolean(PROP_BULKV2_API_ENABLED); + } + + public boolean isRESTAPIEnabled() { + return getBoolean(PROP_UPDATE_WITH_EXTERNALID); + } + + private boolean isBulkApiOperation() { + return getOperationInfo().bulkAPIEnabled(); + } + + public OperationInfo getOperationInfo() { + return getEnum(OperationInfo.class, PROP_OPERATION); + } + + public String getCsvEncoding(boolean isWrite) { + // charset is for CSV read unless isWrite is set to true + String configProperty = PROP_READ_UTF8; + if (isWrite) { + configProperty = PROP_WRITE_UTF8; + logger.debug("Getting charset for writing to CSV"); + } else { + logger.debug("Getting charset for reading from CSV"); + } + String charset = getDefaultCharsetForCsvReadWrite(); + if (getBoolean(configProperty)) { + logger.debug("Using UTF8 charset because '" + + configProperty + +"' is set to true"); + charset = StandardCharsets.UTF_8.name(); + } else { + if (isWrite) { + charset = getString(PROP_WRITE_CHARSET); + } else { + charset = getString(PROP_READ_CHARSET); + } + boolean validCharset = false; + for (String charsetName : Charset.availableCharsets().keySet()) { + if (charset.equalsIgnoreCase(charsetName)) { + validCharset = true; + break; + } + } + if (!validCharset) { + logger.warn("configured charset" + charset + " is not supported"); + charset = getDefaultCharsetForCsvReadWrite(); + } + } + logger.debug("Using charset " + charset); + return charset; + } + + private static String defaultCharsetForCsvReadWrite = null; + private synchronized static String getDefaultCharsetForCsvReadWrite() { + if (defaultCharsetForCsvReadWrite != null) { + return defaultCharsetForCsvReadWrite; + } + String fileEncodingStr = System.getProperty("file.encoding"); + if (fileEncodingStr != null && !fileEncodingStr.isBlank()) { + for (String charsetName : Charset.availableCharsets().keySet()) { + if (fileEncodingStr.equalsIgnoreCase(charsetName)) { + logger.debug("Setting the default charset for CSV read and write to the value of file.encoding system property: " + fileEncodingStr); + defaultCharsetForCsvReadWrite = charsetName; + return charsetName; + } + } + logger.debug("Unable to find the charset '" + + fileEncodingStr + + "' specified in file.encoding system property among available charsets for the Java VM." ); + } + logger.debug("Using JVM default charset as the default charset for CSV read and write : " + Charset.defaultCharset().name()); + defaultCharsetForCsvReadWrite = Charset.defaultCharset().name(); + return defaultCharsetForCsvReadWrite; + } + + private final List listeners = new ArrayList(); + + public synchronized void addListener(ConfigListener l) { + listeners.add(l); + } + + private synchronized void configChanged(String key, String oldValue, String newValue) { + for (ConfigListener l : this.listeners) { + l.configValueChanged(key, oldValue, newValue); + } + } + + public String getOAuthEnvironmentString(String environmentName, String name) { + return getString(OAUTH_PREFIX + environmentName + "." + name); + } + + public String getOAuthEnvironmentPropertyName(String environmentName, String propertyPartialName) { + return OAUTH_PREFIX + environmentName + "." + propertyPartialName; + } + + public void setOAuthEnvironmentString(String environmentName, String name, String... values) { + setValue(OAUTH_PREFIX + environmentName + "." + name, values); + } + + public void setServerEnvironment(String environment) { + if (environment == null || environment.isBlank()) { + environment = SERVER_PROD_ENVIRONMENT_VAL; + } + String[] envArray = getString(PROP_SERVER_ENVIRONMENTS).split(","); + boolean isEnvMatch = false; + for (String env : envArray) { + env = env.strip(); + if (env.equalsIgnoreCase(environment)) { + isEnvMatch = true; + } + } + if (!isEnvMatch) { + environment = SERVER_PROD_ENVIRONMENT_VAL; + } + setValue(PROP_SELECTED_SERVER_ENVIRONMENT, environment); + } + + public String getOAuthClientSecretForCurrentEnv() { + return getOAuthEnvironmentString(getString(PROP_SELECTED_SERVER_ENVIRONMENT), + CLIENTSECRET_LITERAL); + } + + public String getOAuthRedirectURIForCurrentEnv() { + return getAuthEndpointForCurrentEnv() + AppConfig.OAUTH_REDIRECT_URI_SUFFIX; + } + + public String getClientIDForCurrentEnv() { + String clientId; + String environment = getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT); + if (getBoolean(PROP_BULK_API_ENABLED) || getBoolean(PROP_BULKV2_API_ENABLED)) { + clientId = getOAuthEnvironmentString(environment, BULK_CLIENTID_LITERAL); + } else { + clientId = getOAuthEnvironmentString(environment, PARTNER_CLIENTID_LITERAL); + } + if (clientId == null || clientId.isEmpty()) { + clientId = getOAuthEnvironmentString(environment, CLIENTID_LITERAL); + } + return clientId; + } + + public static synchronized String getConfigurationsDir() { + if (AppConfig.configurationsDir == null) { + setConfigurationsDir(null); + } + return AppConfig.configurationsDir; + } + + private static String getDefaultConfigDir() { + return AppUtil.getDirContainingClassJar(AppConfig.class) + + System.getProperty("file.separator") + + AppConfig.CONFIG_DIR_DEFAULT_VALUE; + } + + private static synchronized void setConfigurationsDir(Map argsMap) { + if (argsMap != null && argsMap.containsKey(CLI_OPTION_CONFIG_DIR_PROP)) { + AppConfig.configurationsDir = argsMap.get(CLI_OPTION_CONFIG_DIR_PROP); + } else if (AppConfig.configurationsDir != null && !AppConfig.configurationsDir.isEmpty()) { + return; + } else { + // first time invocation and configurationsDir is not set through argsMap + AppConfig.configurationsDir = System.getProperty(CLI_OPTION_CONFIG_DIR_PROP); + if (AppConfig.configurationsDir == null || AppConfig.configurationsDir.isBlank()) { + AppConfig.configurationsDir = AppConfig.getDefaultConfigDir(); + } + } + File configDirFile = new File(AppConfig.configurationsDir); + try { + AppConfig.configurationsDir = configDirFile.getCanonicalPath(); + } catch (IOException e) { + logger.error("Unable to find configuration folder " + AppConfig.configurationsDir); + AppConfig.configurationsDir = configDirFile.getAbsolutePath(); + } + System.setProperty(CLI_OPTION_CONFIG_DIR_PROP, AppConfig.configurationsDir); + } + + private static synchronized String[] initializeAppConfig(String[] args) throws FactoryConfigurationError, IOException, ConfigInitializationException { + Map argsMap = AppUtil.convertCommandArgsArrayToArgMap(args); + AppConfig.setConfigurationsDir(argsMap); + LoggingUtil.initializeLog(argsMap); + return AppUtil.convertCommandArgsMapToArgsArray(argsMap); + } + + /** + * Create folder provided from the parameter + * + * @param dirPath - folder to be created + * @return True if folder was created successfully or folder already existed False if + * folder was failed to create + */ + private static boolean createDir(File dirPath) { + boolean isSuccessful = true; + if (!dirPath.exists() || !dirPath.isDirectory()) { + isSuccessful = dirPath.mkdirs(); + if (isSuccessful) { + logger.debug("Created config folder: " + dirPath); + } else { + logger.warn("Unable to create config folder: " + dirPath); + } + } else { + logger.debug("Config folder already exists: " + dirPath); + } + return isSuccessful; + } + + /** + * Get the current config.properties and load it into the config bean. + * @throws ConfigInitializationException + * @throws IOException + * @throws FactoryConfigurationError + */ + public static synchronized AppConfig getInstance(Map argMap) throws ConfigInitializationException, FactoryConfigurationError, IOException { + if (argMap == null) { + argMap = new HashMap(); + } + AppConfig.initializeAppConfig(AppUtil.convertCommandArgsMapToArgsArray(argMap)); + + String configurationsDirPath = AppConfig.getConfigurationsDir(); + File configurationsDir; + final String DEFAULT_CONFIG_FILE = "defaultConfig.properties"; //$NON-NLS-1$ + + configurationsDir = new File(configurationsDirPath); + + // Create dir if it doesn't exist + boolean isMkdirSuccessfulOrExisting = createDir(configurationsDir); + if (!isMkdirSuccessfulOrExisting) { + String errorMsg = Messages.getMessage(AppConfig.class, "errorCreatingOutputDir", configurationsDirPath); + logger.error(errorMsg); + throw new ConfigInitializationException(errorMsg); + } + + // check if the config file exists + File configFile = new File(configurationsDir.getAbsolutePath(), CONFIG_FILE); + + String configFilePath = configFile.getAbsolutePath(); + logger.debug("Looking for file in config path: " + configFilePath); + if (!configFile.exists()) { + + File defaultConfigFile = new File(configurationsDir, DEFAULT_CONFIG_FILE); + logger.debug("Looking for file in config file " + defaultConfigFile.getAbsolutePath()); + // If default config exists, copy the default to user config + // If doesn't exist, create a blank user config + + if (defaultConfigFile.exists()) { + try { + // Copy default config to user config + logger.info(String.format("User config file does not exist in '%s' Default config file is copied from '%s'", + configFilePath, defaultConfigFile.getAbsolutePath())); + Files.copy(defaultConfigFile.toPath(), configFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + String errorMsg = String.format("Failed to copy '%s' to '%s'", defaultConfigFile.getAbsolutePath(), configFile); + logger.warn(errorMsg, e); + throw new ConfigInitializationException(errorMsg, e); + } + } else { + // extract from the jar + try { + AppUtil.extractFromJar("/" + CONFIG_FILE, configFile); + } catch (IOException e) { + // TODO Auto-generated catch block + logger.error("Unable to extract " + CONFIG_FILE + " from jar " + e.getMessage()); + } + } + configFile.setWritable(true); + configFile.setReadable(true); + } else { + logger.debug("User config is found in " + configFile.getAbsolutePath()); + } + + AppConfig appConfig = null; + try { + appConfig = new AppConfig(configFilePath, argMap); + currentConfig = appConfig; + logger.debug(Messages.getMessage(AppConfig.class, "configInit")); //$NON-NLS-1$ + } catch (IOException | ProcessInitializationException e) { + logger.error(e.getMessage()); + throw new ConfigInitializationException(Messages.getMessage(AppConfig.class, "errorConfigLoad", configFilePath), e); + } + return appConfig; + } + + private static AppConfig currentConfig = null; + public static AppConfig getCurrentConfig() { + return currentConfig; + } + + public static interface ConfigListener { + void configValueChanged(String key, String oldValue, String newValue); + } +} diff --git a/src/main/java/com/salesforce/dataloader/config/Config.java b/src/main/java/com/salesforce/dataloader/config/Config.java deleted file mode 100644 index 8b66a21c4..000000000 --- a/src/main/java/com/salesforce/dataloader/config/Config.java +++ /dev/null @@ -1,1050 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.config; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PrintStream; -import java.io.PrintWriter; -import java.nio.charset.Charset; -import java.security.GeneralSecurityException; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; - -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.exception.ConfigInitializationException; -import com.salesforce.dataloader.exception.ParameterLoadException; -import com.salesforce.dataloader.security.EncryptionUtil; - -/** - * @author Lexi Viripaeff - * @since 6.0 - */ -public class Config { - private static Logger logger = Logger.getLogger(Config.class); - - /** - * Default values for specific parameters - */ - public static final int DEFAULT_EXTRACT_REQUEST_SIZE = 500; - public static final int DEFAULT_MIN_RETRY_SECS = 2; - public static final int DEFAULT_MAX_RETRIES = 3; - public static final int MAX_RETRIES_LIMIT = 10; - public static final int DEFAULT_CONNECTION_TIMEOUT_SECS = 60; - public static final int DEFAULT_TIMEOUT_SECS = 540; - public static final int DEFAULT_LOAD_BATCH_SIZE = 200; - public static final int DEFAULT_DAO_WRITE_BATCH_SIZE = 500; - public static final int DEFAULT_DAO_READ_BATCH_SIZE = 200; - public static final int MAX_LOAD_BATCH_SIZE = 200; - public static final int MAX_DAO_READ_BATCH_SIZE = 200; - public static final int MAX_DAO_WRITE_BATCH_SIZE = 2000; - public static final int MAX_BULK_API_BATCH_BYTES = 10000000; - public static final int MAX_BULK_API_BATCH_SIZE = 10000; - public static final int DEFAULT_BULK_API_BATCH_SIZE = 2000; - public static final long DEFAULT_BULK_API_CHECK_STATUS_INTERVAL = 5000L; - public static final String DEFAULT_ENDPOINT_URL = "https://login.salesforce.com"; - - /* - * Issue #59 - Dataloader will not read all the database rows to get a total count - * when skipTotalCount = "false" - * - * The default is "true" - */ - public static final Boolean DEFAULT_SKIP_TOTAL_COUNT = true; - /** - * Constants that were made not configurable by choice - */ - public static final String ID_COLUMN_NAME = "ID"; //$NON-NLS-1$ - public static final String ERROR_COLUMN_NAME = "ERROR"; //$NON-NLS-1$ - public static final String STATUS_COLUMN_NAME = "STATUS"; //$NON-NLS-1$ - - /** - * The mapping from preference name to preference value (represented as strings). - */ - private final Properties properties; - - /** - * The default-default value for the respective types. - */ - public static final boolean BOOLEAN_DEFAULT = false; - public static final double DOUBLE_DEFAULT = 0.0; - public static final float FLOAT_DEFAULT = 0.0f; - public static final int INT_DEFAULT = 0; - public static final long LONG_DEFAULT = 0L; - public static final String STRING_DEFAULT = ""; //$NON-NLS-1$ - public static final Map MAP_STRING_DEFAULT = new LinkedHashMap<>(); - - /** - * The Constants for the current Loader Keys - */ - // - // salesforce constants - - // Loader Preferences - public static final String HIDE_WELCOME_SCREEN = "loader.hideWelcome"; - - // Delimiter settings - public static final String CSV_DELIMETER_COMMA = "loader.csvComma"; - public static final String CSV_DELIMETER_TAB = "loader.csvTab"; - public static final String CSV_DELIMETER_OTHER = "loader.csvOther"; - public static final String CSV_DELIMETER_OTHER_VALUE = "loader.csvOtherValue"; - - //Special Internal Configs - public static final String SFDC_INTERNAL = "sfdcInternal"; //$NON-NLS-1$ - public static final String SFDC_INTERNAL_IS_SESSION_ID_LOGIN = "sfdcInternal.isSessionIdLogin"; //$NON-NLS-1$ - public static final String SFDC_INTERNAL_SESSION_ID = "sfdcInternal.sessionId"; //$NON-NLS-1$ - - // salesforce client connectivity - public static final String USERNAME = "sfdc.username"; //$NON-NLS-1$ - public static final String PASSWORD = "sfdc.password"; //$NON-NLS-1$ - public static final String ENDPOINT = "sfdc.endpoint"; //$NON-NLS-1$ - public static final String PROXY_HOST = "sfdc.proxyHost"; //$NON-NLS-1$ - public static final String PROXY_PORT = "sfdc.proxyPort"; //$NON-NLS-1$ - public static final String PROXY_USERNAME = "sfdc.proxyUsername"; //$NON-NLS-1$ - public static final String PROXY_PASSWORD = "sfdc.proxyPassword"; //$NON-NLS-1$ - public static final String PROXY_NTLM_DOMAIN = "sfdc.proxyNtlmDomain"; //$NON-NLS-1$ - public static final String TIMEOUT_SECS = "sfdc.timeoutSecs"; //$NON-NLS-1$ - public static final String CONNECTION_TIMEOUT_SECS = "sfdc.connectionTimeoutSecs"; //$NON-NLS-1$ - public static final String NO_COMPRESSION = "sfdc.noCompression"; //$NON-NLS-1$ - public static final String ENABLE_RETRIES = "sfdc.enableRetries"; //$NON-NLS-1$ - public static final String MAX_RETRIES = "sfdc.maxRetries"; //$NON-NLS-1$ - public static final String MIN_RETRY_SLEEP_SECS = "sfdc.minRetrySleepSecs"; //$NON-NLS-1$ - public static final String DEBUG_MESSAGES = "sfdc.debugMessages"; //$NON-NLS-1$ - public static final String DEBUG_MESSAGES_FILE = "sfdc.debugMessagesFile"; //$NON-NLS-1$ - public static final String RESET_URL_ON_LOGIN = "sfdc.resetUrlOnLogin"; //$NON-NLS-1$ - public static final String TRUNCATE_FIELDS = "sfdc.truncateFields";//$NON-NLS-1$ - public static final String BULK_API_ENABLED = "sfdc.useBulkApi"; - public static final String BULK_API_SERIAL_MODE = "sfdc.bulkApiSerialMode"; - public static final String BULK_API_CHECK_STATUS_INTERVAL = "sfdc.bulkApiCheckStatusInterval"; - public static final String BULK_API_ZIP_CONTENT = "sfdc.bulkApiZipContent"; - public static final String WIRE_OUTPUT = "sfdc.wireOutput"; - public static final String TIMEZONE = "sfdc.timezone"; - - public static final String OAUTH_PARTIAL_BULK = "bulk"; - public static final String OAUTH_PARTIAL_PARTNER = "partner"; - public static final String OAUTH_PARTIAL_SERVER = "server"; - public static final String OAUTH_PARTIAL_CLIENTSECRET = "clientsecret"; - public static final String OAUTH_PARTIAL_CLIENTID = "clientid"; - public static final String OAUTH_PARTIAL_REDIRECTURI = "redirecturi"; - public static final String OAUTH_PARTIAL_BULK_CLIENTID = OAUTH_PARTIAL_BULK + "." + OAUTH_PARTIAL_CLIENTID; - public static final String OAUTH_PARTIAL_PARTNER_CLIENTID = OAUTH_PARTIAL_PARTNER + "." + OAUTH_PARTIAL_CLIENTID; - public static final String OAUTH_ENVIRONMENTS = "sfdc.oauth.environments"; - public static final String OAUTH_ENVIRONMENT = "sfdc.oauth.environment"; - public static final String OAUTH_ACCESSTOKEN = "sfdc.oauth.accesstoken"; - public static final String OAUTH_REFRESHTOKEN = "sfdc.oauth.refreshtoken"; - public static final String OAUTH_SERVER = "sfdc.oauth." + OAUTH_PARTIAL_SERVER; - public static final String OAUTH_CLIENTSECRET = "sfdc.oauth." + OAUTH_PARTIAL_CLIENTSECRET; - public static final String OAUTH_CLIENTID = "sfdc.oauth." + OAUTH_PARTIAL_CLIENTID; - public static final String OAUTH_REDIRECTURI = "sfdc.oauth." + OAUTH_PARTIAL_REDIRECTURI; - - // salesforce operation parameters - public static final String INSERT_NULLS = "sfdc.insertNulls"; //$NON-NLS-1$ - public static final String ENTITY = "sfdc.entity"; //$NON-NLS-1$ - public static final String LOAD_BATCH_SIZE = "sfdc.loadBatchSize"; //$NON-NLS-1$ - public static final String ASSIGNMENT_RULE = "sfdc.assignmentRule"; //$NON-NLS-1$ - public static final String EXTERNAL_ID_FIELD = "sfdc.externalIdField"; //$NON-NLS-1$ - public static final String EXTRACT_REQUEST_SIZE = "sfdc.extractionRequestSize"; //$NON-NLS-1$ - public static final String EXTRACT_SOQL = "sfdc.extractionSOQL"; //$NON-NLS-1$ - - // - // process configuration (action parameters) - // - public static final String OPERATION = "process.operation"; //$NON-NLS-1$ - public static final String MAPPING_FILE = "process.mappingFile"; //$NON-NLS-1$ - public static final String EURO_DATES = "process.useEuropeanDates"; //$NON-NLS-1$ - - // process configuration - public static final String OUTPUT_STATUS_DIR = "process.statusOutputDirectory"; //$NON-NLS-1$ - public static final String OUTPUT_SUCCESS = "process.outputSuccess"; //$NON-NLS-1$ - public static final String ENABLE_EXTRACT_STATUS_OUTPUT = "process.enableExtractStatusOutput"; //$NON-NLS-1$ - public static final String ENABLE_LAST_RUN_OUTPUT = "process.enableLastRunOutput"; //$NON-NLS-1$ - public static final String LAST_RUN_OUTPUT_DIR = "process.lastRunOutputDirectory"; //$NON-NLS-1$ - public static final String OUTPUT_ERROR = "process.outputError"; //$NON-NLS-1$ - public static final String LOAD_ROW_TO_START_AT = "process.loadRowToStartAt"; //$NON-NLS-1$ - public static final String INITIAL_LAST_RUN_DATE = "process.initialLastRunDate"; - public static final String ENCRYPTION_KEY_FILE = "process.encryptionKeyFile"; //$NON-NLS-1$ - - // data access configuration (e.g., for CSV file, database, etc). - public static final String DAO_TYPE = "dataAccess.type"; //$NON-NLS-1$ - public static final String DAO_NAME = "dataAccess.name"; //$NON-NLS-1$ - public static final String DAO_READ_BATCH_SIZE = "dataAccess.readBatchSize"; - public static final String DAO_WRITE_BATCH_SIZE = "dataAccess.writeBatchSize"; - public static final String DAO_SKIP_TOTAL_COUNT = "dataAccess.skipTotalCount"; - - /* - * TODO: when batching is introduced to the DataAccess, these parameters will become useful - * public static final String DAO_REQUEST_SIZE = "dataAccess.extractionRequestSize"; - * public static final String DAO_BATCH_SIZE = "dataAccess.batchSize"; - */ - public static final String READ_UTF8 = "dataAccess.readUTF8"; //$NON-NLS-1$ - public static final String WRITE_UTF8 = "dataAccess.writeUTF8"; //$NON-NLS-1$ - - /** - * Indicates whether a value as been changed - */ - private boolean dirty = false; - - /** - * The file name used by the load method to load a property file. This filename is - * used to save the properties file when save is called. - */ - private String filename; - /** - * The lastRun is for last run statistics file - */ - private final LastRun lastRun; - /** - * encrypter is a utility used internally in the config for reading/writing - * encrypted values. Right now, the list of encrypted values is known to this class only. - */ - private final EncryptionUtil encrypter = new EncryptionUtil(); - - private boolean isBatchMode = false; - - private final String configDir; - - /** - * dateFormatter will be used for getting dates in/out of the configuration - * file(s) - */ - public static final DateFormat DATE_FORMATTER = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); - - /** - * The string representation used for true("true"). - */ - public static final String TRUE = "true"; //$NON-NLS-1$ - - /** - * The string representation used for false("false"). - */ - public static final String FALSE = "false"; //$NON-NLS-1$ - - /** - * communications with bulk api always use UTF8 - */ - public static final String BULK_API_ENCODING = "UTF-8"; - - /** - * Creates an empty config that loads from and saves to the a file.

Use the methods - * load() and save() to load and store this preference store.

- * - * @param filename the file name - * @see #load() - * @see #save() - */ - public Config(String configDir, String filename, String lastRunFileName) throws ConfigInitializationException { - properties = new LinkedProperties(); - this.configDir = configDir; - this.filename = filename; - // last run gets initialized a little later since config params are needed for that - this.lastRun = new LastRun(lastRunFileName); - } - - /** - * Initialize last run directory and file. This works hand in hand with Config constructor and - * the load. The config needs to be loaded before this. In case of UI, config is loaded once, in - * case of command line, config is loaded and then overrides are loaded. - */ - public void initLastRunFile() { - if (getBoolean(Config.ENABLE_LAST_RUN_OUTPUT)) { - String lastRunDir = getString(Config.LAST_RUN_OUTPUT_DIR); - if (lastRunDir == null || lastRunDir.length() == 0) { - lastRunDir = this.configDir; - } - this.lastRun.init(lastRunDir, true); - try { - this.lastRun.load(); - } catch (IOException e) { - logger.warn(Messages.getFormattedString("LastRun.errorLoading", new String[]{ - this.lastRun.getFullPath(), e.getMessage()}), e); - } - } - } - - private boolean useBulkApiByDefault() { - return false; - } - - /** - * This sets the current defaults. - */ - public void setDefaults() { - setValue(HIDE_WELCOME_SCREEN, true); - - setValue(CSV_DELIMETER_COMMA, true); - setValue(CSV_DELIMETER_TAB, true); - setValue(CSV_DELIMETER_OTHER, false); - setValue(CSV_DELIMETER_OTHER_VALUE, "-"); - - setValue(ENDPOINT, DEFAULT_ENDPOINT_URL); - setValue(LOAD_BATCH_SIZE, useBulkApiByDefault() ? DEFAULT_BULK_API_BATCH_SIZE : DEFAULT_LOAD_BATCH_SIZE); - setValue(LOAD_ROW_TO_START_AT, 0); - setValue(TIMEOUT_SECS, DEFAULT_TIMEOUT_SECS); - setValue(CONNECTION_TIMEOUT_SECS, DEFAULT_CONNECTION_TIMEOUT_SECS); - setValue(ENABLE_RETRIES, true); - setValue(MAX_RETRIES, DEFAULT_MAX_RETRIES); - setValue(MIN_RETRY_SLEEP_SECS, DEFAULT_MIN_RETRY_SECS); - setValue(ASSIGNMENT_RULE, ""); //$NON-NLS-1$ - setValue(INSERT_NULLS, false); - setValue(ENABLE_EXTRACT_STATUS_OUTPUT, false); - setValue(ENABLE_LAST_RUN_OUTPUT, true); - setValue(RESET_URL_ON_LOGIN, true); - setValue(EXTRACT_REQUEST_SIZE, DEFAULT_EXTRACT_REQUEST_SIZE); - setValue(DAO_WRITE_BATCH_SIZE, DEFAULT_DAO_WRITE_BATCH_SIZE); - setValue(DAO_READ_BATCH_SIZE, DEFAULT_DAO_READ_BATCH_SIZE); - setValue(TRUNCATE_FIELDS, true); - // TODO: When we're ready, make Bulk API turned on by default. - setValue(BULK_API_ENABLED, useBulkApiByDefault()); - setValue(BULK_API_SERIAL_MODE, false); - setValue(BULK_API_ZIP_CONTENT, false); - setValue(BULK_API_CHECK_STATUS_INTERVAL, DEFAULT_BULK_API_CHECK_STATUS_INTERVAL); - setValue(WIRE_OUTPUT, false); - setValue(TIMEZONE, TimeZone.getDefault().getID()); - //sfdcInternal settings - setValue(SFDC_INTERNAL, false); - setValue(SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); - setValue(SFDC_INTERNAL_SESSION_ID, (String) null); - - //oauth settings - setValue(OAUTH_SERVER, DEFAULT_ENDPOINT_URL); - setValue(OAUTH_REDIRECTURI, DEFAULT_ENDPOINT_URL); - setValue(OAUTH_ENVIRONMENTS, STRING_DEFAULT); - setValue(OAUTH_ENVIRONMENT, STRING_DEFAULT); - } - - /** - * Returns true if the name is a key in the config - * - * @param name the name of the key - */ - public boolean contains(String name) { - return properties.containsKey(name) || lastRun.hasParameter(name) && lastRun.containsKey(name); - } - - /** - * Gets boolean for a given name - * - * @return boolean - */ - public boolean getBoolean(String name) { - String value = getParamValue(name); - if (value == null || value.length() == 0) return BOOLEAN_DEFAULT; - if (value.equals(Config.TRUE)) return true; - return false; - } - - /** - * Gets double for a given name. - * - * @return double - */ - - public double getDouble(String name) throws ParameterLoadException { - String value = getParamValue(name); - if (value == null || value.length() == 0) return DOUBLE_DEFAULT; - double ival; - try { - ival = new Double(value).doubleValue(); - } catch (NumberFormatException e) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, - Double.class.getName()}); - logger.warn(errMsg, e); - throw new ParameterLoadException(e.getMessage(), e); - } - return ival; - } - - /** - * Gets float for a given name. - * - * @return float - */ - public float getFloat(String name) throws ParameterLoadException { - String value = getParamValue(name); - if (value == null || value.length() == 0) return FLOAT_DEFAULT; - float ival = FLOAT_DEFAULT; - try { - ival = new Float(value).floatValue(); - } catch (NumberFormatException e) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, - Float.class.getName()}); - logger.warn(errMsg, e); - throw new ParameterLoadException(e.getMessage(), e); - } - return ival; - } - - /** - * Gets int for a given name. - * - * @return int - */ - public int getInt(String name) throws ParameterLoadException { - String value = getParamValue(name); - if (value == null || value.length() == 0) return INT_DEFAULT; - int ival = 0; - try { - ival = Integer.parseInt(value); - } catch (NumberFormatException e) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, - Integer.class.getName()}); - logger.warn(errMsg, e); - throw new ParameterLoadException(e.getMessage(), e); - } - return ival; - } - - /** - * Gets long for a given name. - * - * @return long - */ - public long getLong(String name) throws ParameterLoadException { - String value = getParamValue(name); - if (value == null || value.length() == 0) return LONG_DEFAULT; - long ival = LONG_DEFAULT; - try { - ival = Long.parseLong(value); - } catch (NumberFormatException e) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, - Long.class.getName()}); - logger.warn(errMsg, e); - throw new ParameterLoadException(e.getMessage(), e); - } - return ival; - } - - /** - * Gets required string param for a given name. If not found, throws an exception - * - * @return string - */ - public String getStringRequired(String name) throws ParameterLoadException { - String value = getString(name); - if (value == null || value.length() == 0) { - String errMsg = Messages.getFormattedString("Config.errorNoRequiredParameter", name); //$NON-NLS-1$ - logger.fatal(errMsg); - throw new ParameterLoadException(errMsg); - } - return value; - } - - /** - * Gets string for a given name. - * - * @return string - */ - public String getString(String name) { - String value = getParamValue(name); - if (value == null) return STRING_DEFAULT; - return value; - } - - - public ArrayList getStrings(String name) { - String values = getString(name); - ArrayList list = new ArrayList<>(); - if (values != null && !values.trim().isEmpty()) { - Collections.addAll(list, values.trim().split(",")); - } - return list; - } - - /** - * Gets an enum value for a given config name. - */ - public > T getEnum(Class enumClass, String name) { - return Enum.valueOf(enumClass, getString(name)); - } - - public TimeZone getTimeZone() { - return TimeZone.getTimeZone(getString(TIMEZONE)); - } - - /** - * Gets path to a config file given the config file property name - * - * @param configFileProperty property containing a config filename - * @return Config filename path based on config property value. Config file is assumed to reside - * in the global config directory - */ - public String getConfigFilename(String configFileProperty) { - String value = getParamValue(configFileProperty); - if (value == null) return null; - return constructConfigFilePath(new File(value).getName()); - } - - public String getLastRunFilename() { - return this.lastRun == null ? null : this.lastRun.getFullPath(); - } - - - /** - * Constructs config file path based on the configuration directory and the passed in config - * filename - * - * @param configFilename Config filename that resides in the config directory - * @return Full path to the config file - */ - public String constructConfigFilePath(String configFilename) { - File configPathFile = new File(this.filename).getParentFile(); - return new File(configPathFile, configFilename).getAbsolutePath(); - } - - /** - * @return Date - */ - public Date getDate(String name) throws ParameterLoadException { - String value = getParamValue(name); - if (value == null || value.length() == 0) return Calendar.getInstance().getTime(); - Date dval = null; - try { - dval = DATE_FORMATTER.parse(value); - } catch (ParseException e) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, - Date.class.getName()}); - logger.warn(errMsg, e); - throw new ParameterLoadException(e.getMessage(), e); - } - return dval; - } - - /** - * Get map from a string param value. String format of map is key1=val1,key2=val2,... - * - * @return Map - */ - public Map getMap(String name) throws ParameterLoadException { - String value = getParamValue(name); - if (value == null || value.length() == 0) return MAP_STRING_DEFAULT; - Map mval = new HashMap(); - String[] pairs = value.split(","); - for (String pair : pairs) { - String[] nameValue = pair.split("="); - if (nameValue.length != 2) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{name, - Map.class.getName()}); - logger.warn(errMsg); - throw new ParameterLoadException(errMsg); - } - mval.put(nameValue[0], nameValue[1]); - } - return mval; - } - - /** - * @return parameter value - */ - private String getParamValue(String name) { - String value; - if (lastRun.hasParameter(name)) { - value = lastRun.getProperty(name); - } else { - value = properties != null ? properties.getProperty(name) : null; - } - return value; - } - - /** - * Prints the contents of this preference store to the given print stream. - * - * @param out the print stream - */ - public void list(PrintStream out) { - properties.list(out); - lastRun.list(out); - } - - /** - * Prints the contents of this preference store to the given print writer. - * - * @param out the print writer - */ - public void list(PrintWriter out) { - properties.list(out); - lastRun.list(out); - } - - /** - * Loads this preference store from the file established in the constructor - * Config(java.lang.String) (or by setFileName). Default preference - * values are not affected. - * - * @throws java.io.IOException if there is a problem loading this store - */ - public void load() throws IOException, ConfigInitializationException { - if (filename == null) { - logger.fatal(Messages.getString("Config.fileMissing")); - throw new IOException(Messages.getString("Config.fileMissing")); //$NON-NLS-1$ - } - FileInputStream in = new FileInputStream(filename); - try { - load(in); - } finally { - in.close(); - } - } - - /** - * Loads this preference store from the given input stream. Default preference values are not - * affected. - * - * @param in the input stream - * @throws ConfigInitializationException If there's a problem loading the parameters - * @throws IOException IF there's an I/O problem loading parameter from file - */ - private void load(InputStream in) throws ConfigInitializationException, IOException { - try { - properties.load(in); - } catch (IOException e) { - logger.fatal(Messages.getFormattedString("Config.errorPropertiesLoad", e.getMessage())); - throw e; - } - // paramter post-processing - postLoad(properties); - - dirty = false; - } - - /** - * Post process parameters. Right now, only decrypts encrypted values in the map - * - * @param values Values to be post-processed - */ - @SuppressWarnings("unchecked") - private void postLoad(Map values) throws ConfigInitializationException { - Map propMap = values; - - // initialize encryption - initEncryption(propMap); - - // decrypt encrypted values - if (propMap.containsKey(PASSWORD)) { - String propValue = decryptProperty(encrypter, propMap, PASSWORD, isBatchMode()); - if (propValue == null) propValue = ""; - propMap.put(PASSWORD, propValue); - } - if (propMap.containsKey(PROXY_PASSWORD)) { - String propValue = decryptProperty(encrypter, propMap, PROXY_PASSWORD, isBatchMode()); - if (propValue == null) propValue = ""; - propMap.put(PROXY_PASSWORD, propValue); - } - } - - /** - * Load config parameter override values. The main use case is loading of overrides from - * external config file - */ - public void loadParameterOverrides(Map configOverrideMap) throws ParameterLoadException, - ConfigInitializationException { - // Need to initialize last run date if it's present neither in config or override - if (configOverrideMap.containsKey(INITIAL_LAST_RUN_DATE)) { - lastRun.setDefault(LastRun.LAST_RUN_DATE, configOverrideMap.get(INITIAL_LAST_RUN_DATE)); - } - - // make sure to post process the args to be loaded - postLoad(configOverrideMap); - - // replace values in the Config - putValue(configOverrideMap); - - // make sure that last run file gets the latest configuration - initLastRunFile(); - } - - /** - * Decrypt property with propName using the encrypter. If decryption succeeds, return the - * decrypted value - * - * @return decrypted property value - */ - static private String decryptProperty(EncryptionUtil encrypter, Map propMap, String propName, boolean isBatch) - throws ParameterLoadException { - String propValue = propMap.get(propName); - if (propValue != null && propValue.length() > 0) { - try { - return encrypter.decryptString(propValue); - } catch (GeneralSecurityException e) { - // if running in the UI, we can ignore encryption errors - if (isBatch) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{propName, - String.class.getName()}); - logger.error(errMsg, e); - throw new ParameterLoadException(errMsg, e); - } else { - return null; - } - } catch (Exception e) { - String errMsg = Messages.getFormattedString("Config.errorParameterLoad", new String[]{propName, - String.class.getName()}); - logger.error(errMsg, e); - throw new ParameterLoadException(errMsg, e); - } - } - return propValue; - } - - /** - * @throws ConfigInitializationException - */ - private void initEncryption(Map values) throws ConfigInitializationException { - // initialize encrypter - String keyFile = values.get(ENCRYPTION_KEY_FILE); - if (keyFile != null && keyFile.length() != 0) { - try { - encrypter.setCipherKeyFromFilePath(keyFile); - } catch (IOException e) { - String errMsg = Messages.getFormattedString("Config.errorSecurityInit", new String[]{keyFile, - e.getMessage()}); - logger.error(errMsg); - throw new ConfigInitializationException(errMsg); - } - } - } - - /** - * Returns whether the config needs saving - * - * @return boolean - */ - public boolean needsSaving() { - return dirty; - } - - /** - * Returns an enumeration of all preferences known to this config - * - * @return an array of preference names - */ - public String[] preferenceNames() { - ArrayList list = new ArrayList(); - Enumeration en = properties.propertyNames(); - while (en.hasMoreElements()) { - list.add((String) en.nextElement()); - } - return list.toArray(new String[list.size()]); - } - - /** - * Puts a set of values from a map into config - * - * @param values Map of overriding values - */ - public void putValue(Map values) throws ParameterLoadException, ConfigInitializationException { - for (String key : values.keySet()) { - putValue(key, values.get(key)); - } - } - - /** - * Puts a value into the config - */ - public void putValue(String name, String value) { - String oldValue = getString(name); - if (oldValue == null || !oldValue.equals(value)) { - setValue(name, value); - dirty = true; - } - } - - /** - * Saves the preferences to the file from which they were originally loaded. - * - * @throws java.io.IOException if there is a problem saving this store - */ - public void save() throws IOException, GeneralSecurityException { - if (filename == null) { - throw new IOException(Messages.getString("Config.fileMissing")); //$NON-NLS-1$ - } - - // Secure password code prevents the saving of passwords - // no great way to do this, - String oldPassword = encryptProperty(PASSWORD); - String oldProxyPassword = encryptProperty(PROXY_PASSWORD); - - String oauthAccessToken = getString(OAUTH_ACCESSTOKEN); - String oauthRefreshToken = getString(OAUTH_REFRESHTOKEN); - putValue(OAUTH_ACCESSTOKEN, ""); - putValue(OAUTH_REFRESHTOKEN, ""); - - - FileOutputStream out = null; - try { - out = new FileOutputStream(filename); - save(out, "Loader Config"); //$NON-NLS-1$ - } finally { - if (out != null) { - out.close(); - } - // restore original property values - putValue(PASSWORD, oldPassword); - putValue(PROXY_PASSWORD, oldProxyPassword); - putValue(OAUTH_ACCESSTOKEN, oauthAccessToken); - putValue(OAUTH_REFRESHTOKEN, oauthRefreshToken); - - } - // save last run statistics - lastRun.save(); - } - - - /** - * Save statistics from the last run - */ - public void saveLastRun() throws IOException { - lastRun.save(); - } - - /** - * @param propName name of the property - * @return old value of the property - */ - private String encryptProperty(String propName) throws GeneralSecurityException { - String oldValue = getString(propName); - if (oldValue != null && oldValue.length() > 0) { - putValue(propName, encrypter.encryptString(oldValue)); - } - return oldValue; - } - - /** - * Saves this config to the given output stream. The given string is inserted as header - * information. - * - * @param out the output stream - * @param header the header - * @throws java.io.IOException if there is a problem saving this store - */ - private void save(OutputStream out, String header) throws IOException { - properties.store(out, header); - dirty = false; - } - - public void setValue(String name, Map valueMap) { - StringBuilder sb = new StringBuilder(); - for (String key : valueMap.keySet()) { - // add comma for subsequent entries - if (sb.length() != 0) { - sb.append(","); - } - sb.append(key + "=" + valueMap.get(key)); - } - putValue(name, sb.toString()); - } - - /** - * Sets the name of the file used when loading and storing this config - * - * @param name the file name - * @see #load() - * @see #save() - */ - public void setFilename(String name) { - filename = name; - } - - /** - * @return the current configuration filename - */ - public String getFilename() { - return filename; - } - - - /** - * Sets a double - */ - public void setValue(String name, double value) { - setProperty(name, Double.toString(value)); - } - - /** - * Sets a float - */ - public void setValue(String name, float value) { - setProperty(name, Float.toString(value)); - } - - /** - * Sets an int - */ - public void setValue(String name, int value) { - setProperty(name, Integer.toString(value)); - } - - /** - * Sets a long - */ - public void setValue(String name, long value) { - setProperty(name, Long.toString(value)); - } - - /** - * Sets a string - */ - public void setValue(String name, String... values) { - if (values != null && values.length > 1) { - StringJoiner joiner = new StringJoiner(","); - for (String value : values) { - joiner.add(value); - } - setProperty(name, joiner.toString()); - } else if (values != null && values.length > 0) { - setProperty(name, values[0]); - } else { - setProperty(name, null); - } - - } - - /** - * Sets a boolean - */ - public void setValue(String name, boolean value) { - setProperty(name, Boolean.toString(value)); - } - - public void setValue(String name, Date value) { - setProperty(name, DATE_FORMATTER.format(value)); - } - - /** - * @param name - * @param newValue - */ - private void setProperty(String name, String newValue) { - final String oldValue = getString(name); - final boolean paramChanged = (oldValue == null || oldValue.length() == 0) ? (newValue != null && newValue - .length() > 0) : !oldValue.equals(newValue); - if (paramChanged) { - this.dirty = true; - configChanged(name, oldValue, newValue); - if (lastRun.hasParameter(name)) { - lastRun.put(name, newValue); - } else { - properties.put(name, newValue); - } - - } - } - - public boolean isBatchMode() { - return isBatchMode; - } - - public void setBatchMode(boolean isBatchMode) { - this.isBatchMode = isBatchMode; - } - - public int getLoadBatchSize() { - boolean bulkApi = isBulkAPIEnabled(); - int bs = -1; - try { - bs = getInt(LOAD_BATCH_SIZE); - } catch (ParameterLoadException e) { - } - int maxBatchSize = bulkApi ? MAX_BULK_API_BATCH_SIZE : MAX_LOAD_BATCH_SIZE; - return bs > maxBatchSize ? maxBatchSize : bs > 0 ? bs : getDefaultBatchSize(bulkApi); - } - - public int getDefaultBatchSize(boolean bulkApi) { - return bulkApi ? DEFAULT_BULK_API_BATCH_SIZE : DEFAULT_LOAD_BATCH_SIZE; - } - - public boolean useBulkAPIForCurrentOperation() { - return isBulkAPIEnabled() && isBulkApiOperation(); - } - - public boolean isBulkAPIEnabled() { - return getBoolean(BULK_API_ENABLED); - } - - private boolean isBulkApiOperation() { - return getOperationInfo().bulkAPIEnabled(); - } - - public OperationInfo getOperationInfo() { - return getEnum(OperationInfo.class, OPERATION); - } - - private static final Charset UTF8 = Charset.forName("UTF-8"); - - public String getCsvWriteEncoding() { - if (Charset.defaultCharset().equals(UTF8) || getBoolean(WRITE_UTF8)) return UTF8.name(); - return Charset.defaultCharset().name(); - } - - private final List listeners = new ArrayList(); - - public synchronized void addListener(ConfigListener l) { - listeners.add(l); - } - - private synchronized void configChanged(String key, String oldValue, String newValue) { - for (ConfigListener l : this.listeners) { - l.configValueChanged(key, oldValue, newValue); - } - } - - public String getOAuthEnvironmentString(String environmentName, String name) { - return getString("sfdc.oauth." + environmentName + "." + name); - } - - public void setOAuthEnvironmentString(String environmentName, String name, String... values) { - setValue("sfdc.oauth." + environmentName + "." + name, values); - } - - public void setOAuthEnvironment(String environment) { - String clientId; - if (getBoolean(BULK_API_ENABLED)) { - clientId = getOAuthEnvironmentString(environment, OAUTH_PARTIAL_BULK_CLIENTID); - } else { - clientId = getOAuthEnvironmentString(environment, OAUTH_PARTIAL_PARTNER_CLIENTID); - } - if (clientId == null || clientId.isEmpty()) { - clientId = getOAuthEnvironmentString(environment, OAUTH_PARTIAL_CLIENTID); - } - setValue(OAUTH_ENVIRONMENT, environment); - setValue(OAUTH_SERVER, getOAuthEnvironmentString(environment, OAUTH_PARTIAL_SERVER)); - setValue(OAUTH_CLIENTID, clientId); - setValue(OAUTH_CLIENTSECRET, getOAuthEnvironmentString(environment, OAUTH_PARTIAL_CLIENTSECRET)); - setValue(OAUTH_REDIRECTURI, getOAuthEnvironmentString(environment, OAUTH_PARTIAL_REDIRECTURI)); - } - - public static interface ConfigListener { - void configValueChanged(String key, String oldValue, String newValue); - } - -} diff --git a/src/main/java/com/salesforce/dataloader/config/ConfigPropertyMetadata.java b/src/main/java/com/salesforce/dataloader/config/ConfigPropertyMetadata.java new file mode 100644 index 000000000..52e2c31e5 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/config/ConfigPropertyMetadata.java @@ -0,0 +1,285 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.config; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Map; +import java.util.TreeMap; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.dao.csv.CSVFileWriter; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.model.RowInterface; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.ui.Labels; +import com.salesforce.dataloader.util.AppUtil; + +/* + * Data class capturing information about configuration property (aka Setting). + */ +public class ConfigPropertyMetadata { + private static final TreeMap propertiesMap = new TreeMap(); + + private final String name; + private String defaultValue = ""; + private boolean readOnly = false; + private boolean encrypted = false; + private boolean internal = false; + private boolean commandLineOption = false; + private String uiLabelTemplate = ""; + private String uiTooltipTemplate = ""; + private String description = ""; + + static { + Field[] appConfigFields = AppConfig.class.getDeclaredFields(); + for (Field configField : appConfigFields) { + if ((configField.getName().startsWith("PROP_") && !configField.getName().startsWith("PROP_SFDC_INTERNAL")) + || configField.getName().startsWith("CLI_OPTION_")) { + String propName; + try { + propName = configField.get(null).toString(); + if (propName == null || propName.isBlank() || propName.startsWith(AppConfig.PILOT_PROPERTY_PREFIX)) { + continue; + } + } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { + continue; + } + ConfigPropertyMetadata configProp = new ConfigPropertyMetadata(propName); + propertiesMap.put(propName, configProp); + } + } + Field[] appUtilFields = AppUtil.class.getDeclaredFields(); + for (Field configField : appUtilFields) { + if (configField.getName().startsWith("CLI_OPTION_")) { + String propName; + try { + propName = configField.get(null).toString(); + } catch (SecurityException | IllegalArgumentException | IllegalAccessException e) { + continue; + } + ConfigPropertyMetadata configProp = new ConfigPropertyMetadata(propName); + configProp.setCommandLineOption(true); + configProp.setReadOnly(true); // all command line options are read-only + propertiesMap.put(propName, configProp); + } + } + } + + public ConfigPropertyMetadata(String propName) { + this.name = propName; + this.uiLabelTemplate = Labels.getString("AdvancedSettingsDialog.uiLabel." + propName); + if (this.uiLabelTemplate == null + || (this.uiLabelTemplate.startsWith("!") && this.uiLabelTemplate.endsWith("!"))) { + this.uiLabelTemplate = ""; + } + String tooltipText = null; + tooltipText = Labels.getString("AdvancedSettingsDialog.uiTooltip." + propName); + if (tooltipText != null && tooltipText.startsWith("!") && tooltipText.endsWith("!")) { + tooltipText = null; + } + if (tooltipText == null + || (tooltipText.startsWith("!") && tooltipText.endsWith("!"))) { + tooltipText = null; + } + if (tooltipText == null) { + this.uiTooltipTemplate = ""; + } else { + this.uiTooltipTemplate = tooltipText; + }; + String description = null; + description = Labels.getString("AppConfig.property.description." + propName); + if (description != null && description.startsWith("!") && description.endsWith("!")) { + description = null; + } + if (description == null + || (description.startsWith("!") && description.endsWith("!"))) { + description = null; + } + if (description == null) { + this.description = ""; + } else { + this.description = description; + } + this.encrypted = AppConfig.isEncryptedProperty(propName); + this.readOnly = AppConfig.isReadOnlyProperty(propName); + this.internal = AppConfig.isInternalProperty(propName); + } + + public static Map getPropertiesMap() { + return propertiesMap; + } + public String getDefaultValue() { + return defaultValue; + } + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + public boolean isReadOnly() { + return readOnly; + } + public void setReadOnly(boolean ro) { + this.readOnly = ro; + } + public boolean isEncrypted() { + return encrypted; + } + public boolean isCommandLineOption() { + return commandLineOption; + } + public void setCommandLineOption(boolean commandLineOption) { + this.commandLineOption = commandLineOption; + } + public String getUiLabelTemplate() { + return uiLabelTemplate; + } + public String getUiTooltip() { + return uiTooltipTemplate; + } + + public boolean isInternal() { + return internal; + } + + public String getName() { + return this.name; + } + + public static final String PROPERTIES_CSV = "properties.csv"; + + private static final String COL_PROPERTY_NAME = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_PROPERTY_NAME"); + private static final String COL_DESCRIPTION = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_DESCRIPTION"); + private static final String COL_UI_LABEL = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_UI_LABEL"); + private static final String COL_DEFAULT_VAL = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_DEFAULT_VAL"); + private static final String COL_IS_READ_ONLY = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_IS_READ_ONLY"); + private static final String COL_IS_COMMAND_LINE_OPTION = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_IS_COMMAND_LINE_OPTION"); + private static final String COL_IS_ENCRYPTED = Messages.getString("ConfigPropertyMetadata.csvHeader.COL_IS_ENCRYPTED"); + private static String fullPathToPropsFile = null; + private static final Logger logger = DLLogManager.getLogger(ConfigPropertyMetadata.class); + + public static String getFullPathToPropsFile(AppConfig appConfig) { + if (fullPathToPropsFile != null + && !fullPathToPropsFile.isBlank()) { + return fullPathToPropsFile; + } + if (appConfig == null) { + logger.warn(Messages.getString("ConfigPropertyMetadata.errorGeneratePathToCSV")); + return null; + } + fullPathToPropsFile = appConfig.constructConfigFilePath(PROPERTIES_CSV); + return fullPathToPropsFile; + } + + public static void generateCSV(AppConfig appConfig) { + if (appConfig == null) { + logger.warn(Messages.getString("ConfigPropertyMetadata.errorGenerateCSV")); + return; + } + File propsFile = new File(getFullPathToPropsFile(appConfig)); + if (propsFile.exists()) { + // delete existing file + propsFile.delete(); + } + try { + propsFile.createNewFile(); + } catch (IOException e) { + logger.warn(Messages.getString("ConfigPropertyMetadata.errorGenerateCSV")); + logger.warn(e.getMessage()); + logger.info(e.getStackTrace()); + return; + } + + ArrayList colHeaders = new ArrayList(); + colHeaders.add(COL_PROPERTY_NAME); + colHeaders.add(COL_UI_LABEL); + colHeaders.add(COL_DESCRIPTION); + colHeaders.add(COL_DEFAULT_VAL); + colHeaders.add(COL_IS_READ_ONLY); + colHeaders.add(COL_IS_COMMAND_LINE_OPTION); + colHeaders.add(COL_IS_ENCRYPTED); + + CSVFileWriter csvWriter = new CSVFileWriter( + getFullPathToPropsFile(appConfig), appConfig, AppUtil.COMMA); + try { + csvWriter.open(); + csvWriter.setColumnNames(colHeaders); + } catch (DataAccessObjectInitializationException e) { + logger.warn(Messages.getString("ConfigPropertyMetadata.errorGenerateCSV")); + logger.warn(e.getMessage()); + logger.info(e.getStackTrace()); + return; + } + try { + ArrayList headerLabelList = new ArrayList(); + headerLabelList.add(COL_PROPERTY_NAME); + headerLabelList.add(COL_UI_LABEL); + headerLabelList.add(COL_DESCRIPTION); + headerLabelList.add(COL_DEFAULT_VAL); + headerLabelList.add(COL_IS_READ_ONLY); + headerLabelList.add(COL_IS_COMMAND_LINE_OPTION); + headerLabelList.add(COL_IS_ENCRYPTED); + ArrayList rowList = new ArrayList(propertiesMap.size()); + TableHeader header = new TableHeader(headerLabelList); + + for (ConfigPropertyMetadata propMD : propertiesMap.values()) { + if (propMD.isInternal()) { + continue; + } + TableRow row = new TableRow(header); + row.put(COL_PROPERTY_NAME, propMD.getName()); + row.put(COL_UI_LABEL, propMD.getUiLabelTemplate()); + String description = propMD.getDescription(); + if (description == null || description.isBlank()) { + description = propMD.getUiTooltip(); + } + row.put(COL_DESCRIPTION, description); + row.put(COL_DEFAULT_VAL, propMD.getDefaultValue()); + row.put(COL_IS_READ_ONLY, propMD.isReadOnly()); + row.put(COL_IS_COMMAND_LINE_OPTION, propMD.isCommandLineOption()); + row.put(COL_IS_ENCRYPTED, propMD.isEncrypted()); + rowList.add(row); + } + try { + csvWriter.writeRowList(rowList); + } catch (DataAccessObjectException e) { + logger.warn(e.getStackTrace()); + } + } finally { + logger.debug(Messages.getFormattedString("ConfigPropertyMetadata.infoGeneratedCSVLocation", getFullPathToPropsFile(appConfig))); + csvWriter.close(); + } + } + + public String getDescription() { + return this.description; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/config/LastRun.java b/src/main/java/com/salesforce/dataloader/config/LastRun.java deleted file mode 100644 index 4ef3e81b0..000000000 --- a/src/main/java/com/salesforce/dataloader/config/LastRun.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.config; - -import java.io.*; -import java.util.*; - -import org.apache.log4j.Logger; - -/** - * Class to manage last run information. Currently based on properties. - * - * @author Alex Warshavsky - * @since 8.0 - */ -public class LastRun extends Properties { - /** - * Comment for serialVersionUID - */ - private static final long serialVersionUID = 1L; - - private static Logger logger = Logger.getLogger(LastRun.class); - - // last run statistics - public static final String LAST_LOAD_BATCH_ROW = "process.lastLoadBatchRow"; //$NON-NLS-1$ - public static final String LAST_RUN_DATE = "process.lastRunDate"; //$NON-NLS-1$ - - private static Map paramNames = new HashMap(); - - private String filePath; - private String filename; - private boolean outputEnabled; - - /** - * Initialize lastRun with filename, this is needed if last run output is not enabled (yet) - */ - public LastRun(String filename) { - super(); // init properties - this.filename = filename; - init(null, false); - } - - /** - * Init the class, with path parameter - * - * @param filePath - * @param outputEnabled - */ - public void init(String filePath, boolean outputEnabled) { - this.filePath = filePath; - this.outputEnabled = outputEnabled; - - paramNames.put(LAST_RUN_DATE,""); - paramNames.put(LAST_LOAD_BATCH_ROW,""); - } - - public String getFullPath() { - return new File(this.filePath, this.filename).getAbsolutePath(); - } - - /** - * @throws IOException - */ - public void load() throws IOException { - if(! outputEnabled) { - return; - } - if (filePath == null) { - logger.fatal(Messages.getString("LastRun.fileMissing")); - throw new IOException(Messages.getString("LastRun.fileMissing")); //$NON-NLS-1$ - } - File lastRunFile = new File(filePath, filename); - logger.info(Messages.getFormattedString("LastRun.fileInfo", lastRunFile.getAbsolutePath())); - if(!lastRunFile.exists()) { - lastRunFile.createNewFile(); - } - FileInputStream in = new FileInputStream(lastRunFile); - try { - load(in); - } finally { - in.close(); - } - } - - public void save() throws IOException { - if(! outputEnabled) { - return; - } - if (filePath == null) { - throw new IOException(Messages.getString("LastRun.fileMissing")); //$NON-NLS-1$ - } - - final FileOutputStream out = new FileOutputStream(new File(filePath, filename)); - try { - store(out, "Last Run Config"); //$NON-NLS-1$ - } finally { - out.close(); - } - } - - public boolean hasParameter(String paramName) { - return paramNames.containsKey(paramName); - } - - public void setDefault(String paramName, String paramValue) { - if (!containsKey(paramName)) { - setProperty(paramName, paramValue); - } - } -} diff --git a/src/main/java/com/salesforce/dataloader/config/LastRunProperties.java b/src/main/java/com/salesforce/dataloader/config/LastRunProperties.java new file mode 100644 index 000000000..e0538c509 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/config/LastRunProperties.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.config; + +import java.io.*; +import java.util.*; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +/** + * Class to manage last run information. Currently based on properties. + * + * @author Alex Warshavsky + * @since 8.0 + */ +public class LastRunProperties extends Properties { + /** + * Comment for serialVersionUID + */ + private static final long serialVersionUID = 1L; + + private static Logger logger = DLLogManager.getLogger(LastRunProperties.class); + + // last run statistics + public static final String LAST_LOAD_BATCH_ROW = "process.lastLoadBatchRow"; //$NON-NLS-1$ + public static final String LAST_RUN_DATE = "process.lastRunDate"; //$NON-NLS-1$ + + private static Map paramNames = new HashMap(); + + private String filePath; + private String filename; + private boolean outputEnabled; + + /** + * Initialize lastRun with filename, this is needed if last run output is not enabled (yet) + */ + public LastRunProperties(String filename, String filePath, boolean outputEnabled) { + super(); // init properties + this.filename = filename; + this.filePath = filePath; + this.outputEnabled = outputEnabled; + + paramNames.put(LAST_RUN_DATE,""); + paramNames.put(LAST_LOAD_BATCH_ROW,""); + } + + public String getFullPath() { + return new File(this.filePath, this.filename).getAbsolutePath(); + } + + /** + * @throws IOException + */ + public void load() throws IOException { + if(! outputEnabled) { + return; + } + if (filePath == null) { + logger.fatal(Messages.getString("LastRun.fileMissing")); + throw new IOException(Messages.getString("LastRun.fileMissing")); //$NON-NLS-1$ + } + File lastRunFile = new File(filePath, filename); + logger.debug(Messages.getFormattedString("LastRun.fileInfo", lastRunFile.getAbsolutePath())); + if(!lastRunFile.exists()) { + lastRunFile.createNewFile(); + } + FileInputStream in = new FileInputStream(lastRunFile); + try { + load(in); + } finally { + in.close(); + } + } + + public void save() throws IOException { + if(! outputEnabled) { + return; + } + if (filePath == null) { + throw new IOException(Messages.getString("LastRun.fileMissing")); //$NON-NLS-1$ + } + + final FileOutputStream out = new FileOutputStream(new File(filePath, filename)); + try { + store(out, "Last Run Config"); //$NON-NLS-1$ + } finally { + out.close(); + } + } + + public boolean hasParameter(String paramName) { + return paramNames.containsKey(paramName); + } + + public void setDefault(String paramName, String paramValue) { + if (!containsKey(paramName)) { + setProperty(paramName, paramValue); + } + } +} diff --git a/src/main/java/com/salesforce/dataloader/config/LinkedProperties.java b/src/main/java/com/salesforce/dataloader/config/LinkedProperties.java index 9e289c288..3cf24bbec 100644 --- a/src/main/java/com/salesforce/dataloader/config/LinkedProperties.java +++ b/src/main/java/com/salesforce/dataloader/config/LinkedProperties.java @@ -34,6 +34,7 @@ /** * A simple replacement for properties that will allow us to maintain order on a proprties object */ +@SuppressWarnings("serial") public class LinkedProperties extends Properties { private final LinkedHashMap sorted =new LinkedHashMap<>(); diff --git a/src/main/java/com/salesforce/dataloader/controller/Controller.java b/src/main/java/com/salesforce/dataloader/controller/Controller.java index 5846ef544..fddbcc50c 100644 --- a/src/main/java/com/salesforce/dataloader/controller/Controller.java +++ b/src/main/java/com/salesforce/dataloader/controller/Controller.java @@ -25,34 +25,61 @@ */ package com.salesforce.dataloader.controller; -import java.io.*; -import java.security.GeneralSecurityException; -import java.text.SimpleDateFormat; -import java.util.*; - -import javax.xml.parsers.FactoryConfigurationError; - -import org.apache.log4j.Logger; - import com.salesforce.dataloader.action.IAction; import com.salesforce.dataloader.action.OperationInfo; import com.salesforce.dataloader.action.progress.ILoaderProgress; -import com.salesforce.dataloader.client.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.client.BulkV1Client; +import com.salesforce.dataloader.client.BulkV2Client; +import com.salesforce.dataloader.client.ClientBase; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.client.CompositeRESTClient; +import com.salesforce.dataloader.client.ReferenceEntitiesDescribeMap; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.dao.DataAccessObject; import com.salesforce.dataloader.dao.DataAccessObjectFactory; -import com.salesforce.dataloader.exception.*; -import com.salesforce.dataloader.mapping.*; +import com.salesforce.dataloader.exception.ConfigInitializationException; +import com.salesforce.dataloader.exception.ControllerInitializationException; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.MappingInitializationException; +import com.salesforce.dataloader.exception.OperationException; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.exception.ProcessInitializationException; +import com.salesforce.dataloader.mapping.LoadMapper; +import com.salesforce.dataloader.mapping.Mapper; +import com.salesforce.dataloader.mapping.SOQLMapper; +import com.salesforce.dataloader.ui.Labels; import com.salesforce.dataloader.ui.LoaderWindow; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.soap.partner.DescribeGlobalSObjectResult; import com.sforce.soap.partner.DescribeSObjectResult; +import com.sforce.soap.partner.LimitInfo; import com.sforce.ws.ConnectionException; -import org.apache.log4j.xml.DOMConfigurator; +import com.sforce.ws.ConnectorConfig; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.GeneralSecurityException; +import java.text.SimpleDateFormat; +import java.util.Collection; +import java.util.Date; +import java.util.Map; +import java.util.Properties; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** - * The class that controls dataloader engine (config, salesforce communication, mapping, dao). For UI, this is the - * controller for all the underlying data access. + * The class that controls dataloader engine (config, salesforce communication, mapping, dao). For + * UI, this is the controller for all the underlying data access. * * @author Lexi Viripaeff * @author Alex Warshavsky @@ -60,65 +87,125 @@ */ public class Controller { - private static final String LOG_CONF_OVERRIDE = "log-conf.xml"; - private static boolean isLogInitialized = false; // make sure log is initialized only once - - /** the system property name used to determine the config directory */ - public static final String CONFIG_DIR_PROP = "salesforce.config.dir"; - - public static final String CONFIG_FILE = "config.properties"; //$NON-NLS-1$ - private static final String LAST_RUN_FILE_SUFFIX = "_lastRun.properties"; //$NON-NLS-1$ - private static final String CONFIG_DIR = "conf"; //$NON-NLS-1$ - private static String APP_NAME; //$NON-NLS-1$ - public static String APP_VERSION; //$NON-NLS-1$ - public static String API_VERSION; - private static String APP_VENDOR; //$NON-NLS-1$ + /** + * the system property name used to determine the config folder + */ + public static String APP_VERSION = ""; //$NON-NLS-1$ + public static String API_VERSION = ""; + /** - * config is an instance of configuration that's tied to this instance of controller in a multithreaded - * environment + * config is an instance of configuration that's tied to this instance of + * controller in a multithreaded environment */ - private Config config; + private AppConfig appConfig; private Mapper mapper; private DataAccessObjectFactory daoFactory; private DataAccessObject dao; - private BulkClient bulkClient; + private BulkV1Client bulkV1Client; + private BulkV2Client bulkV2Client; private PartnerClient partnerClient; + private CompositeRESTClient restClient; + private LoaderWindow loaderWindow; + private boolean lastOperationSuccessful = true; // logger - private static Logger logger = Logger.getLogger(Controller.class); - private String appPath; - - private Controller(String name, boolean isBatchMode) throws ControllerInitializationException { - // load app version properties + private static Logger logger = DLLogManager.getLogger(Controller.class); + + private IAction lastExecutedAction = null; + + static { Properties versionProps = new Properties(); try { - versionProps.load(this.getClass().getClassLoader().getResourceAsStream("com/salesforce/dataloader/version.properties")); + versionProps.load(Controller.class.getClassLoader().getResourceAsStream("com/salesforce/dataloader/version.properties")); + APP_VERSION = versionProps.getProperty("dataloader.version"); } catch (IOException e) { - throw new ControllerInitializationException(e); + logger.error("Unable to read version.properties file from uber jar"); } - APP_NAME = versionProps.getProperty("dataloader.name"); - APP_VENDOR = versionProps.getProperty("dataloader.vendor"); - // FIXME clean this up, make static - // dataloader version has 3 parts, salesforce app api version should match first two parts - APP_VERSION = versionProps.getProperty("dataloader.version"); - String[] dataloaderVersion = APP_VERSION.split("\\."); - API_VERSION = dataloaderVersion[0] + "." + dataloaderVersion[1]; + } + private Controller(Map argsMap) throws ControllerInitializationException { // if name is passed to controller, use it to create a unique run file name - initConfig(name, isBatchMode); - } + try { + this.appConfig = AppConfig.getInstance(argsMap); + } catch (Exception e) { + logger.fatal("Controller: Exception happened in initializing AppConfig:", e); + throw new ControllerInitializationException(e.getMessage()); + } - public void setConfigDefaults() { - config.setDefaults(); + if (daoFactory == null) { + daoFactory = new DataAccessObjectFactory(); + } + getLatestDownloadableDataLoaderVersion(); + } + + private static String latestDownloadableDataLoaderVersion = null; + private static final String DL_DOWNLOADABLE_REGEX = "[0-9]+\\.[0-9]+\\.[0-9]+\\.zip"; + public synchronized String getLatestDownloadableDataLoaderVersion() { + if (latestDownloadableDataLoaderVersion != null) { + return latestDownloadableDataLoaderVersion; + } + try { + ConnectorConfig connConfig = new ConnectorConfig(); + AppUtil.setConnectorConfigProxySettings(appConfig, connConfig); + HttpClientTransport clientTransport = HttpClientTransport.getInstance(); + clientTransport.setConfig(connConfig); + InputStream inputStream = clientTransport.httpGet(AppUtil.DATALOADER_DOWNLOAD_URL); + + String responseContent = new String(inputStream.readAllBytes()); + Pattern htmlTagInRichTextPattern = Pattern.compile(DL_DOWNLOADABLE_REGEX); + Matcher matcher = htmlTagInRichTextPattern.matcher(responseContent); + String downloadableVersion = AppUtil.DATALOADER_VERSION; + if (matcher.find()) { + downloadableVersion = matcher.group(); + downloadableVersion = downloadableVersion.substring(0, downloadableVersion.lastIndexOf(".")); + } + latestDownloadableDataLoaderVersion = downloadableVersion; + return downloadableVersion; + } catch (Exception e) { + logger.info("Unable to check for the latest available data loader version: " + e.getMessage()); + latestDownloadableDataLoaderVersion = AppUtil.DATALOADER_VERSION; + return AppUtil.DATALOADER_VERSION; + } } - + public synchronized void executeAction(ILoaderProgress monitor) throws DataAccessObjectException, OperationException { - OperationInfo operation = this.config.getOperationInfo(); + OperationInfo operation = this.appConfig.getOperationInfo(); IAction action = operation.instantiateAction(this, monitor); logger.info(Messages.getFormattedString("Controller.executeStart", operation)); //$NON-NLS-1$ + logger.debug("API info for the operation:" + getAPIInfo()); action.execute(); + this.getClient().getSession().performedSessionActivity(); // reset session activity timer + this.lastExecutedAction = action; + } + + public IAction getLastExecutedAction() { + return this.lastExecutedAction; + } + + public String getAPIInfo() { + if (this.partnerClient == null) { + return null; + } + String apiTypeStr = "SOAP API"; + if (appConfig.isBulkAPIEnabled()) { + apiTypeStr = "Bulk API"; + } + if (appConfig.isBulkV2APIEnabled()) { + apiTypeStr = "Bulk API 2.0"; + } + String[] args = {apiTypeStr, PartnerClient.getAPIVersionForTheSession()}; + String apiInfoStr = Labels.getFormattedString("Operation.apiVersion", args); + LimitInfo apiLimitInfo = this.partnerClient.getAPILimitInfo(); + if (apiLimitInfo != null) { + apiInfoStr = Labels.getFormattedString("Operation.currentAPIUsage", apiLimitInfo.getCurrent()) + + "\n" + + Labels.getFormattedString("Operation.apiLimit", apiLimitInfo.getLimit()) + + "\n" + + apiInfoStr; + } + return apiInfoStr; } private void validateSession() { @@ -134,19 +221,29 @@ public void setReferenceDescribes() throws ConnectionException { validateSession(); getPartnerClient().setFieldReferenceDescribes(); } + + public void setReferenceDescribes(Collection sfFields) throws ConnectionException { + validateSession(); + getPartnerClient().setFieldReferenceDescribes(sfFields); + } - private boolean loginIfSessionExists(ClientBase clientToLogin) { + private boolean connectIfSessionExists(ClientBase clientToLogin) { if (!isLoggedIn()) return false; return clientToLogin.connect(getPartnerClient().getSession()); } - - public boolean loginIfSessionExists() { - return loginIfSessionExists(getClient()); - } - - public boolean setEntityDescribes() throws ConnectionException { - validateSession(); - return getPartnerClient().setEntityDescribes(); + + public static String getAPIVersion() { + return ClientBase.getAPIVersionForTheSession(); + } + + public static int getAPIMajorVersion() { + String apiFullVersion = Controller.getAPIVersion(); + int apiMajorVersion = 0; + if (apiFullVersion != null) { + String[] apiVersionParts = apiFullVersion.split("\\."); + apiMajorVersion = Integer.parseInt(apiVersionParts[0]); + } + return apiMajorVersion; } public Map getEntityDescribes() { @@ -159,7 +256,7 @@ public DescribeSObjectResult getFieldTypes() { return getPartnerClient().getFieldTypes(); } - public Map getReferenceDescribes() { + public ReferenceEntitiesDescribeMap getReferenceDescribes() { validateSession(); return getPartnerClient().getReferenceDescribes(); } @@ -178,31 +275,60 @@ public boolean isLoggedIn() { return getPartnerClient().isLoggedIn(); } - public void createDao() throws DataAccessObjectInitializationException { + private void createDao(String daoTypeStr, String daoNameStr) throws DataAccessObjectInitializationException { + appConfig.setValue(AppConfig.PROP_DAO_NAME, daoNameStr); + appConfig.setValue(AppConfig.PROP_DAO_TYPE, daoTypeStr); try { - config.getStringRequired(Config.DAO_NAME); // verify required param exists: dao name - dao = daoFactory.getDaoInstance(config.getStringRequired(Config.DAO_TYPE), config); + appConfig.getStringRequired(AppConfig.PROP_DAO_NAME); // verify required param exists: dao name + dao = daoFactory.getDaoInstance(appConfig.getStringRequired(AppConfig.PROP_DAO_TYPE), appConfig); + logger.info(Messages.getString("Process.checkingDao")); //$NON-NLS-1$ + dao.checkConnection(); } catch (Exception e) { logger.fatal(Messages.getString("Controller.errorDAOCreate"), e); //$NON-NLS-1$ throw new DataAccessObjectInitializationException(Messages.getString("Controller.errorDAOCreate"), e); //$NON-NLS-1$ } } + + public void initializeOperation(String daoTypeStr, String daoNameStr, String sObjectName) throws MappingInitializationException { + try { + createDao(daoTypeStr, daoNameStr); + } catch (DataAccessObjectInitializationException e) { + throw new MappingInitializationException(e.getMessage()); + } + appConfig.setValue(AppConfig.PROP_ENTITY, sObjectName); + initializeMapping(); // initialize mapping before setting reference describes + try { + this.setFieldTypes(); + this.setReferenceDescribes(); + } catch (Exception e) { + throw new MappingInitializationException(e); + } + } + + private void initializeMapping() throws MappingInitializationException { + String mappingFile = appConfig.getString(AppConfig.PROP_MAPPING_FILE); + if (mappingFile != null + && !mappingFile.isBlank() && !Files.exists(Path.of(mappingFile))) { + throw new MappingInitializationException("Mapping file " + mappingFile + " does not exist"); + } + if (AppUtil.getAppRunMode() == AppUtil.APP_RUN_MODE.UI) { + mappingFile = null; // Do not use mapping file value set in config.properties in the interactive (UI) mode + } + // Initialize mapping + this.mapper = getAppConfig().getOperationInfo().isExtraction() ? + new SOQLMapper(getPartnerClient(), dao.getColumnNames(), getFieldTypes().getFields(), mappingFile) + : new LoadMapper(getPartnerClient(), dao.getColumnNames(), getFieldTypes().getFields(), mappingFile); - public void createMapper() throws MappingInitializationException { - String mappingFile = config.getString(Config.MAPPING_FILE); - this.mapper = getConfig().getOperationInfo().isExtraction() ? new SOQLMapper(getPartnerClient(), - dao.getColumnNames(), getFieldTypes().getFields(), mappingFile) : new LoadMapper(getPartnerClient(), dao.getColumnNames(), - getFieldTypes().getFields(), mappingFile); } public void createAndShowGUI() throws ControllerInitializationException { // check config access for saving settings -- required in UI - File configFile = new File(config.getFilename()); + File configFile = new File(appConfig.getFilename()); if (!configFile.canWrite()) { - String errMsg = Messages.getFormattedString("Controller.errorConfigWritable", config.getFilename()); + String errMsg = Messages.getFormattedString("Controller.errorConfigWritable", appConfig.getFilename()); String currentWorkingDir = System.getProperty("user.dir"); - if (currentWorkingDir.startsWith("/Volumes")){ + if (currentWorkingDir.startsWith("/Volumes")) { //user is trying to launch dataloader from the dmg. this is not supported errMsg = Messages.getString("Controller.errorConfigWritableDmg"); } @@ -211,239 +337,100 @@ public void createAndShowGUI() throws ControllerInitializationException { throw new ControllerInitializationException(errMsg); } // start the loader UI - new LoaderWindow(this).run(); + this.loaderWindow = new LoaderWindow(this); + this.loaderWindow.run(); saveConfig(); } - - public static Controller getInstance(String name, boolean isBatchMode) throws ControllerInitializationException { - return new Controller(name, isBatchMode); + + public void updateLoaderWindowTitleAndCacheUserInfoForTheSession() { + if (isLoggedIn()) { + try { + ConnectorConfig sessionConfig = getPartnerClient().getConnection().getConfig(); + URL sessionURL = new URL(sessionConfig.getServiceEndpoint()); + String sessionHost = sessionURL.getHost(); + this.loaderWindow.updateTitle(sessionHost); + return; + } catch (MalformedURLException e) { + logger.error(e.getMessage()); + } + } else { + this.loaderWindow.updateTitle(null); + } + } + + public LoaderWindow getLoaderWindow() { + return this.loaderWindow; } + public static synchronized Controller getInstance(Map argsMap) throws ControllerInitializationException, ParameterLoadException, ConfigInitializationException { + return new Controller(argsMap); + } + public synchronized boolean saveConfig() { try { - config.save(); + appConfig.save(); } catch (IOException e) { - logger.fatal(Messages.getFormattedString("Controller.errorConfigSave", config.getFilename()), e); //$NON-NLS-1$ + logger.fatal(Messages.getFormattedString("Controller.errorConfigSave", appConfig.getFilename()), e); //$NON-NLS-1$ return false; } catch (GeneralSecurityException e) { - logger.fatal(Messages.getFormattedString("Controller.errorConfigSave", config.getFilename()), e); //$NON-NLS-1$ + logger.fatal(Messages.getFormattedString("Controller.errorConfigSave", appConfig.getFilename()), e); //$NON-NLS-1$ return false; } return true; } - - /** - * Copies a file - * - * @param file - * the file to copy from - * @param destFile - * the file to copy to - */ - private void copyFile(File file, File destFile) { - BufferedInputStream in = null; - BufferedOutputStream out = null; - try { - - // reading and writing 8 KB at a time - int bufferSize = 8 * 1024; - - byte[] buf = new byte[bufferSize]; - - in = new BufferedInputStream(new FileInputStream(file), bufferSize); - int r = 0; - - out = new BufferedOutputStream(new FileOutputStream(destFile), bufferSize); - - while ((r = in.read(buf, 0, buf.length)) != -1) { - out.write(buf, 0, r); - } - out.flush(); - - } catch (IOException ioe) { - logger.error("Cannot copy file " + file.getAbsolutePath()); - } finally { - if (in != null) try { - in.close(); - } catch (Exception e) {} - if (out != null) try { - out.close(); - } catch (Exception e) {} - } + + public PartnerClient getPartnerClient() { + if (this.partnerClient == null) this.partnerClient = new PartnerClient(this); + return this.partnerClient; } - /** - * Get the current config.properties and load it into the config bean. - * - * @param isBatchMode - * @throws ControllerInitializationException - */ - protected void initConfig(String name, boolean isBatchMode) throws ControllerInitializationException { - - // see if we are ui based - String appdataDir = System.getProperty("appdata.dir"); - String configPath; - String lastRunFileName = name + LAST_RUN_FILE_SUFFIX; - if (appdataDir != null && appdataDir.length() > 0) { - - String appVendorDir = new File(appdataDir, APP_VENDOR).getAbsolutePath(); - // the application directory is versioned to support multiple versions of the loader running in parallel - File appDir = new File(appVendorDir, getProductName()); - appPath = appDir.getAbsolutePath(); - if (!appDir.exists() || !appDir.isDirectory()) { - if (!appDir.mkdirs()) { - logger.warn("Unable to create configuration directory"); - } - } - - // look for config directory under user home directory - String oldConfigDir = new File(System.getProperty("user.dir"), CONFIG_DIR).getAbsolutePath(); - - // check if the files exist - File configFile = new File(appDir, CONFIG_FILE); - configPath = configFile.getAbsolutePath(); - if (!configFile.exists()) { - File oldConfig = new File(oldConfigDir, CONFIG_FILE); - if (!oldConfig.exists()) { - logger.warn("Cannot find default configuration file"); - } else { - copyFile(oldConfig, configFile); - } - } - - } else { - // nope we are running commandline - - // FIXME PLEASE - String configDirPath = getConfigDir(); - logger.debug("config dir: " + configDirPath); - // if configDir is null, set it to target, which is the maven build output directory - // TODO: FIXME - if (configDirPath == null) { - configDirPath = "target"; - logger.warn("config dir was null, so config dir has been set to " + configDirPath); - } - - // TODO: we should log creating new files/directories - // TODO: why did we add this? - File configDir = new File(configDirPath); - if (!(configDir.isDirectory() || configDir.mkdirs())) { - throw new ControllerInitializationException("could not create configuration directory: " + configDir); + public ClientBase getClient() { + if (this.appConfig.useBulkAPIForCurrentOperation()) { + if (this.appConfig.isBulkV2APIEnabled()) { + return getBulkV2Client(); } else { - logger.info("config dir created at " + configDir.getAbsolutePath()); + return getBulkV1Client(); } - - // let the File class combine the dir and file names - File configFile = new File(configDirPath, CONFIG_FILE); - - // TODO: why did we add this? - if (!configFile.exists()) try { - configFile.createNewFile(); - configFile.setWritable(true); - configFile.setReadable(true); - logger.info("config file created at " + configFile.getAbsolutePath()); - } catch (IOException e) { - throw new ControllerInitializationException(e); - } - - this.appPath = configDirPath; - configPath = configFile.getAbsolutePath(); - } - - initLog(); - - try { - config = new Config(getAppPath(), configPath, lastRunFileName); - // set default before actual values are loaded - config.setDefaults(); - config.setBatchMode(isBatchMode); - config.load(); - logger.info(Messages.getMessage(getClass(), "configInit")); //$NON-NLS-1$ - } catch (IOException e) { - throw new ControllerInitializationException(Messages.getMessage(getClass(), "errorConfigLoad", configPath), - e); - } catch (ProcessInitializationException e) { - throw new ControllerInitializationException(Messages.getMessage(getClass(), "errorConfigLoad", configPath), - e); - } - - if (daoFactory == null) { - daoFactory = new DataAccessObjectFactory(); + } else if (this.appConfig.isRESTAPIEnabled()) { + return getRESTClient(); } + return getPartnerClient(); } - /** - * @return product name - */ - private String getProductName() { - return APP_NAME + " " + APP_VERSION; - } - - public static synchronized void initLog() throws FactoryConfigurationError { - // init the log if not initialized already - if (Controller.isLogInitialized) { return; } - try { - File logConfXml = new File(System.getProperty("user.dir"), LOG_CONF_OVERRIDE); - if(logConfXml.exists()) { - logger.info("Reading log-conf.xml in " + logConfXml.getAbsolutePath()); - if(logConfXml.canRead()) { - DOMConfigurator.configure(logConfXml.getAbsolutePath()); - } else { - logger.warn("Unable to read log-conf.xml in " + logConfXml.getAbsolutePath()); - } - } else { - logger.info("Using built-in logging configuration, no log-conf.xml in " + logConfXml.getAbsolutePath()); - } - logger.info(Messages.getString("Controller.logInit")); //$NON-NLS-1$ - } catch (Exception ex) { - logger.error(Messages.getString("Controller.errorLogInit")); //$NON-NLS-1$ - logger.error(ex.toString()); - System.exit(1); + public BulkV1Client getBulkV1Client() { + if (this.bulkV1Client == null) { + this.bulkV1Client = new BulkV1Client(this); + connectIfSessionExists(this.bulkV1Client); } - Controller.isLogInitialized = true; + return this.bulkV1Client; } - - public static String getConfigDir() { - return System.getProperty(CONFIG_DIR_PROP); - } - - public PartnerClient getPartnerClient() { - if (this.partnerClient == null) this.partnerClient = new PartnerClient(this); - return this.partnerClient; - } - - private ClientBase getClient() { - return this.config.useBulkAPIForCurrentOperation() ? getBulkClient() : getPartnerClient(); - } - - public BulkClient getBulkClient() { - if (this.bulkClient == null) { - this.bulkClient = new BulkClient(this); - loginIfSessionExists(this.bulkClient); + + public BulkV2Client getBulkV2Client() { + if (this.bulkV2Client == null) { + this.bulkV2Client = new BulkV2Client(this); + connectIfSessionExists(this.bulkV2Client); } - return this.bulkClient; + return this.bulkV2Client; } - - /** + + public CompositeRESTClient getRESTClient() { + if (this.restClient == null) { + this.restClient = new CompositeRESTClient(this); + connectIfSessionExists(this.restClient); + } + return this.restClient; + }/** * @return Instance of configuration */ - public Config getConfig() { - return config; + public AppConfig getAppConfig() { + return appConfig; } public DataAccessObject getDao() { return dao; } - public void setLoaderConfig(Config config_) { - config = config_; - } - - public String getAppPath() { - return appPath; - } - public Mapper getMapper() { return this.mapper; } @@ -451,13 +438,13 @@ public Mapper getMapper() { public void setStatusFiles(String statusDirName, boolean createDir, boolean generateFiles) throws ProcessInitializationException { File statusDir = new File(statusDirName); - // if status directory unspecified, create one based on config path + // if status folder unspecified, create one based on config path if (statusDirName == null || statusDirName.length() == 0) { - statusDir = new File(new File(appPath), "../status"); + statusDir = new File(new File(AppConfig.getConfigurationsDir()), "../status"); statusDirName = statusDir.getAbsolutePath(); } - // it's an error if directory files exists but not a directory - // or if directory doesn't exist and cannot be created (determined by caller) + // it's an error if folder files exists but not a folder + // or if folder doesn't exist and cannot be created (determined by caller) if (statusDir.exists() && !statusDir.isDirectory()) { throw new ProcessInitializationException(Messages.getFormattedString("Controller.invalidOutputDir", statusDirName)); @@ -466,48 +453,64 @@ public void setStatusFiles(String statusDirName, boolean createDir, boolean gene throw new ProcessInitializationException(Messages.getFormattedString("Controller.invalidOutputDir", statusDirName)); } else { - if (!statusDir.mkdirs()) { throw new ProcessInitializationException(Messages.getFormattedString( - "Controller.errorCreatingOutputDir", statusDirName)); } + if (!statusDir.mkdirs()) { + throw new ProcessInitializationException(Messages.getFormattedString( + "Config.errorCreatingOutputDir", statusDirName)); + } } } - - Date currentTime = new Date(); - SimpleDateFormat format = new SimpleDateFormat("MMddyyhhmmssSSS"); //$NON-NLS-1$ - String timestamp = format.format(currentTime); - // if status files are not specified, generate the files automatically - String successPath = config.getString(Config.OUTPUT_SUCCESS); + String successPath = appConfig.getString(AppConfig.PROP_OUTPUT_SUCCESS); if (generateFiles || successPath == null || successPath.length() == 0) { - successPath = new File(statusDir, "success" + timestamp + ".csv").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ + successPath = new File(statusDir, "success" + getFormattedCurrentTimestamp() + ".csv").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ } - String errorPath = config.getString(Config.OUTPUT_ERROR); + String errorPath = appConfig.getString(AppConfig.PROP_OUTPUT_ERROR); if (generateFiles || errorPath == null || errorPath.length() == 0) { - errorPath = new File(statusDir, "error" + timestamp + ".csv").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ + errorPath = new File(statusDir, "error" + getFormattedCurrentTimestamp() + ".csv").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ } + /* + * TODO: Bulk V2 has the endpoint to download unprocessed records from the submitted + * job. Uncomment the following lines to download them. + String unprocessedRecordsPath = config.getString(Config.OUTPUT_UNPROCESSED_RECORDS); + if (generateFiles || unprocessedRecordsPath == null || unprocessedRecordsPath.length() == 0) { + unprocessedRecordsPath = new File(statusDir, "unprocessedRecords" + timestamp + ".csv").getAbsolutePath(); //$NON-NLS-1$ //$NON-NLS-2$ + } + */ // next validate the error and success csv try { validateFile(successPath); validateFile(errorPath); + // TODO for unprocessed records + // validateFile(unprocessedRecordsPath); } catch (IOException e) { throw new ProcessInitializationException(e.getMessage(), e); } - config.setValue(Config.OUTPUT_STATUS_DIR, statusDirName); - config.setValue(Config.OUTPUT_SUCCESS, successPath); - config.setValue(Config.OUTPUT_ERROR, errorPath); + appConfig.setValue(AppConfig.PROP_OUTPUT_STATUS_DIR, statusDirName); + appConfig.setValue(AppConfig.PROP_OUTPUT_SUCCESS, successPath); + appConfig.setValue(AppConfig.PROP_OUTPUT_ERROR, errorPath); + // TODO for unprocessed records + // config.setValue(Config.OUTPUT_UNPROCESSED_RECORDS, unprocessedRecordsPath); + } + + public String getFormattedCurrentTimestamp() { + Date currentTime = new Date(); + SimpleDateFormat format = new SimpleDateFormat("MMddyyhhmmssSSS"); //$NON-NLS-1$ + return format.format(currentTime); } private void validateFile(String filePath) throws IOException { File file = new File(filePath); // finally make sure the output isn't the data access file - String daoName = config.getString(Config.DAO_NAME); + String daoName = appConfig.getString(AppConfig.PROP_DAO_NAME); // if it doesn't exist and we can create it, its valid if (!file.exists()) { try { - if (!file.createNewFile()) { throw new IOException(Messages.getMessage(getClass(), - "errorFileCreate", filePath)); //$NON-NLS-1$ + if (!file.createNewFile()) { + throw new IOException(Messages.getMessage(getClass(), + "errorFileCreate", filePath)); //$NON-NLS-1$ } } catch (IOException iox) { throw new IOException(Messages.getMessage(getClass(), "errorFileCreate", filePath)); @@ -521,15 +524,34 @@ else if (filePath.equals(daoName)) public void logout() { if (this.partnerClient != null) this.partnerClient.logout(); - this.bulkClient = null; + this.bulkV1Client = null; this.partnerClient = null; + this.bulkV2Client = null; + this.restClient = null; + appConfig.setValue(AppConfig.PROP_OAUTH_ACCESSTOKEN, ""); } public boolean attachmentsEnabled() { - return !getConfig().useBulkAPIForCurrentOperation() || getConfig().getBoolean(Config.BULK_API_ZIP_CONTENT); + if (getAppConfig().isBulkV2APIEnabled()) { + return false; + } else { + return getAppConfig().getBoolean(AppConfig.PROP_BULK_API_ZIP_CONTENT); + } } public void clearMapper() { + if (this.dao != null) { + this.dao.close(); + this.dao = null; + } this.mapper = null; } + + public void setLastOperationSuccessful(boolean successful) { + this.lastOperationSuccessful = successful; + } + + public boolean isLastOperationSuccessful() { + return this.lastOperationSuccessful; + } } diff --git a/src/main/java/com/salesforce/dataloader/dao/AbstractDataReaderImpl.java b/src/main/java/com/salesforce/dataloader/dao/AbstractDataReaderImpl.java new file mode 100644 index 000000000..b91cede1b --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dao/AbstractDataReaderImpl.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.dao; + +import java.util.ArrayList; +import java.util.List; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.DAORowUtil; + +public abstract class AbstractDataReaderImpl implements DataReader { + private AppConfig appConfig; + private DAORowCache rowCache = new DAORowCache(); + private int currentRowNumber; + private int totalRows = 0; + private TableHeader tableHeader = null; + private List daoColsList = new ArrayList(); + + public AbstractDataReaderImpl(AppConfig appConfig) { + this.appConfig = appConfig; + } + + public List readTableRowList(int maxRows) throws DataAccessObjectException { + List rowList = null; + if (this.rowCache.size() > this.currentRowNumber + maxRows) { + rowList = this.rowCache.getRows(currentRowNumber, maxRows); + currentRowNumber = currentRowNumber + rowList.size(); + return rowList; + } else { + return readTableRowListFromDAO(maxRows); + } + } + + public void open() throws DataAccessObjectInitializationException{ + if (isOpenFlag()) { + close(); + } + if (!appConfig.getBoolean(AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || rowCache.size() == 0) { + openDAO(); + this.daoColsList = initializeDaoColumnsList(); + initializeTableHeader(); + } + currentRowNumber = 0; + rowCache.resetCurrentRowIndex(); + setOpenFlag(true); + } + + private void initializeTableHeader() { + if (this.daoColsList == null) { + this.daoColsList = new ArrayList(); + } + ArrayList tableHeaderCols = new ArrayList<>(this.daoColsList); + if (tableHeaderCols.get(0) == null + || !tableHeaderCols.get(0).equalsIgnoreCase(AppConfig.ID_COLUMN_NAME)) { + tableHeaderCols.add(0, AppConfig.ID_COLUMN_NAME); + } + if (!tableHeaderCols.contains(AppConfig.STATUS_COLUMN_NAME)) { + tableHeaderCols.add(AppConfig.STATUS_COLUMN_NAME); + } + if (!tableHeaderCols.contains(AppConfig.ERROR_COLUMN_NAME)) { + tableHeaderCols.add(AppConfig.ERROR_COLUMN_NAME); + } + this.tableHeader = new TableHeader(tableHeaderCols); + } + + public TableRow readTableRow() throws DataAccessObjectException { + if (!isOpenFlag()) { + open(); + } + + // look in the cache first + TableRow trow = rowCache.getCurrentRow(); + if (trow != null) { + currentRowNumber++; + return trow; + } + // not found in cache. Try from DAO. + trow = readTableRowFromDAO(); + if (trow == null) { + this.totalRows = currentRowNumber; + return null; + } + currentRowNumber++; + if (appConfig.getBoolean(AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO)) { + rowCache.addRow(trow); + } + return trow; + } + + public int getCurrentRowNumber() { + return this.currentRowNumber; + } + + public int getTotalRows() throws DataAccessObjectException { + if (totalRows == 0) { + if (!isOpenFlag()) { + open(); + } + totalRows = DAORowUtil.calculateTotalRows(this); + } + return totalRows; + } + + protected AppConfig getAppConfig() { + return this.appConfig; + } + + protected TableHeader getTableHeader() { + return this.tableHeader; + } + + @Override + public List getColumnNames() { + return new ArrayList<>(this.daoColsList); + } + + abstract protected void setOpenFlag(boolean open); + abstract protected boolean isOpenFlag(); + abstract protected void openDAO() throws DataAccessObjectInitializationException; + abstract protected List readTableRowListFromDAO(int maxRows) throws DataAccessObjectException; + abstract protected TableRow readTableRowFromDAO() throws DataAccessObjectException; + abstract protected List initializeDaoColumnsList() throws DataAccessObjectInitializationException; +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/dao/DAORowCache.java b/src/main/java/com/salesforce/dataloader/dao/DAORowCache.java new file mode 100644 index 000000000..6848a7834 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dao/DAORowCache.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.dao; + +import java.util.ArrayList; +import java.util.List; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.model.TableRow; + +public class DAORowCache { + private ArrayList rowList = new ArrayList(); + private int currentRowIndex = 0; + + public DAORowCache() { + } + + public void resetCurrentRowIndex() { + currentRowIndex = 0; + } + + public TableRow getCurrentRow() { + AppConfig appConfig = AppConfig.getCurrentConfig(); + if (currentRowIndex >= rowList.size() + || !appConfig.getBoolean(AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO)) { + return null; + } + return rowList.get(currentRowIndex++); + } + + public void addRow(TableRow row) { + // add a row to the cache only if it is not cached already + if (currentRowIndex >= rowList.size()) { + rowList.add(row); + } + currentRowIndex++; + } + + public int size() { + return rowList.size(); + } + + public List getRows(int startRow, int numRows) { + if (rowList.size() <= startRow) { + return null; + } + if (rowList.size() < startRow + numRows) { + numRows = rowList.size()-startRow; + } + currentRowIndex = startRow + numRows; + return rowList.subList(startRow, startRow + numRows); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/dao/DataAccessObjectFactory.java b/src/main/java/com/salesforce/dataloader/dao/DataAccessObjectFactory.java index 912cb3975..6b48f7852 100644 --- a/src/main/java/com/salesforce/dataloader/dao/DataAccessObjectFactory.java +++ b/src/main/java/com/salesforce/dataloader/dao/DataAccessObjectFactory.java @@ -25,9 +25,13 @@ */ package com.salesforce.dataloader.dao; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; -import com.salesforce.dataloader.config.Config; +import java.io.File; + +import com.salesforce.dataloader.util.DLLogManager; + +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.dao.csv.CSVFileWriter; @@ -37,27 +41,27 @@ import com.salesforce.dataloader.exception.UnsupportedDataAccessObjectException; public class DataAccessObjectFactory { - private static Logger logger = Logger.getLogger(DataAccessObjectFactory.class); + private static Logger logger = DLLogManager.getLogger(DataAccessObjectFactory.class); static public final String CSV_READ_TYPE = "csvRead"; static public final String CSV_WRITE_TYPE = "csvWrite"; static public final String DATABASE_READ_TYPE = "databaseRead"; static public final String DATABASE_WRITE_TYPE = "databaseWrite"; - public DataAccessObject getDaoInstance(String daoType, Config config) + public DataAccessObject getDaoInstance(String daoType, AppConfig appConfig) throws DataAccessObjectInitializationException { DataAccessObject dao = null; - logger.info(Messages.getFormattedString("DataAccessObjectFactory.creatingDao", new String[] {config.getString(Config.DAO_NAME), daoType})); + logger.info(Messages.getFormattedString("DataAccessObjectFactory.creatingDao", new String[] {appConfig.getString(AppConfig.PROP_DAO_NAME), daoType})); if (CSV_READ_TYPE.equalsIgnoreCase(daoType)) { - dao = new CSVFileReader(config); + dao = new CSVFileReader(new File(appConfig.getString(AppConfig.PROP_DAO_NAME)), appConfig, false, false); } else if (CSV_WRITE_TYPE.equalsIgnoreCase(daoType)) { - dao = new CSVFileWriter(config.getString(Config.DAO_NAME), config); + dao = new CSVFileWriter(appConfig.getString(AppConfig.PROP_DAO_NAME), appConfig, appConfig.getString(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS)); } else if (DATABASE_READ_TYPE.equalsIgnoreCase(daoType)) { - dao = new DatabaseReader(config); + dao = new DatabaseReader(appConfig); } else if (DATABASE_WRITE_TYPE.equalsIgnoreCase(daoType)) { - dao = new DatabaseWriter(config); + dao = new DatabaseWriter(appConfig); } else { String errMsg = Messages.getFormattedString("DataAccessObjectFactory.daoTypeNotSupported", daoType); logger.error(errMsg); diff --git a/src/main/java/com/salesforce/dataloader/dao/DataReader.java b/src/main/java/com/salesforce/dataloader/dao/DataReader.java index 783044a9d..a7061f46e 100644 --- a/src/main/java/com/salesforce/dataloader/dao/DataReader.java +++ b/src/main/java/com/salesforce/dataloader/dao/DataReader.java @@ -27,10 +27,10 @@ package com.salesforce.dataloader.dao; import java.util.List; -import java.util.Map; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; /** * Interface to be implemented for data readers -- data access objects that are used for reading rows of data. @@ -39,14 +39,13 @@ * @since 8.0 */ public interface DataReader extends DataAccessObject { - /** * Get a row of data from a data source * * @return a {@link Row} containing all the keys and values of a row * @throws DataAccessObjectException */ - Row readRow() throws DataAccessObjectException; + TableRow readTableRow() throws DataAccessObjectException; /** * Get a list of rows of data from a data source @@ -55,7 +54,7 @@ public interface DataReader extends DataAccessObject { * @return a list of up to maxRows {@link Row} objects, each of them containing all the keys and values of a row * @throws DataAccessObjectException */ - List readRowList(int maxRows) throws DataAccessObjectException; + List readTableRowList(int maxRows) throws DataAccessObjectException; /** * @return Total number of rows that will be read by the current Data Access Object diff --git a/src/main/java/com/salesforce/dataloader/dao/DataWriter.java b/src/main/java/com/salesforce/dataloader/dao/DataWriter.java index f45fc4bd3..3f747f057 100644 --- a/src/main/java/com/salesforce/dataloader/dao/DataWriter.java +++ b/src/main/java/com/salesforce/dataloader/dao/DataWriter.java @@ -27,11 +27,10 @@ package com.salesforce.dataloader.dao; import java.util.List; -import java.util.Map; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.RowInterface; /** * Interface to be implemented for data writers -- data access objects that are used for writing rows of data. @@ -54,12 +53,12 @@ public interface DataWriter extends DataAccessObject { * @return Any data columns generated as a result of writing * @throws DataAccessObjectException */ - boolean writeRow(Row inputRow) throws DataAccessObjectException; + boolean writeRow(RowInterface inputRow) throws DataAccessObjectException; /** * @param inputRowList * @return List of data rows with generated data columns as a result of writing * @throws DataAccessObjectException */ - boolean writeRowList(List inputRowList) throws DataAccessObjectException; + boolean writeRowList(List inputRowList) throws DataAccessObjectException; } diff --git a/src/main/java/com/salesforce/dataloader/dao/EncryptedDataSource.java b/src/main/java/com/salesforce/dataloader/dao/EncryptedDataSource.java index 154f3f91d..b1e3bc8b1 100644 --- a/src/main/java/com/salesforce/dataloader/dao/EncryptedDataSource.java +++ b/src/main/java/com/salesforce/dataloader/dao/EncryptedDataSource.java @@ -25,14 +25,11 @@ */ package com.salesforce.dataloader.dao; -import java.io.IOException; -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.security.EncryptionUtil; import com.salesforce.dataloader.config.Messages; -import java.security.GeneralSecurityException; +import com.salesforce.dataloader.security.EncryptionAesUtil; -import org.apache.commons.dbcp.BasicDataSource; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; /* * This class can be substituted for org.apache.commons.dbcp.BasicDataSource in @@ -57,14 +54,14 @@ * */ -public class EncryptedDataSource extends org.apache.commons.dbcp.BasicDataSource { +public class EncryptedDataSource extends org.apache.commons.dbcp2.BasicDataSource { public EncryptedDataSource() { super(); } public synchronized void setPassword(String encodedPassword) { - this.password = decode(encodedPassword); + super.setPassword(decode(encodedPassword)); } private String decode(String password) { @@ -78,11 +75,11 @@ private String decode(String password) { * @return decrypted property value * @throws ParameterLoadException */ - static private String DecryptString(EncryptionUtil encrypter, String encryptedString) { + static private String DecryptString(EncryptionAesUtil encrypter, String encryptedString) { if (encryptedString != null && encryptedString.length() > 0) { try { - return encrypter.decryptString(encryptedString); + return encrypter.decryptMsg(encryptedString); } catch (Exception e) { @@ -95,7 +92,7 @@ static private String DecryptString(EncryptionUtil encrypter, String encryptedSt return null; } - private final EncryptionUtil encrypter = new EncryptionUtil(); + private final EncryptionAesUtil encrypter = new EncryptionAesUtil(); - private static Logger logger = Logger.getLogger(EncryptedDataSource.class); + private static Logger logger = DLLogManager.getLogger(EncryptedDataSource.class); } diff --git a/src/main/java/com/salesforce/dataloader/dao/csv/CSVColumnVisitor.java b/src/main/java/com/salesforce/dataloader/dao/csv/CSVColumnVisitor.java index 69431c104..573bca63a 100644 --- a/src/main/java/com/salesforce/dataloader/dao/csv/CSVColumnVisitor.java +++ b/src/main/java/com/salesforce/dataloader/dao/csv/CSVColumnVisitor.java @@ -29,7 +29,8 @@ import java.io.IOException; import java.io.Writer; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; /** * Describe your class here. @@ -40,17 +41,22 @@ public class CSVColumnVisitor { private boolean first = true; + private boolean escapeFormulaValue = true; private static final char QUOTE = '"'; private static final char COMMA = ','; + private static final char EQUAL = '='; private Writer writer; + private char columnDelimiter = COMMA; //logger - private static Logger logger = Logger.getLogger(CSVColumnVisitor.class); + private static Logger logger = DLLogManager.getLogger(CSVColumnVisitor.class); - public CSVColumnVisitor(Writer writer) { + public CSVColumnVisitor(Writer writer, boolean escapeFormulaValue, char columnDelimiter) { this.writer = writer; + this.escapeFormulaValue = escapeFormulaValue; + this.columnDelimiter = columnDelimiter; } public void newRow() { @@ -64,7 +70,7 @@ public void visit(String column) throws IOException { } try { if (!first) - writer.write(COMMA); + writer.write(this.columnDelimiter); else first = false; @@ -72,6 +78,9 @@ public void visit(String column) throws IOException { for (int i = 0, len = column.length(); i < len; i++) { char c = column.charAt(i); + if (this.escapeFormulaValue && i == 0 && c == EQUAL) { + writer.write("'"); // escape the '=' character as the first char + } if (c == QUOTE) writer.write("\"\""); else diff --git a/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileReader.java b/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileReader.java index b11b438a0..8ce6db442 100644 --- a/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileReader.java +++ b/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileReader.java @@ -31,20 +31,24 @@ import java.io.FileNotFoundException; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; +import org.apache.commons.io.ByteOrderMark; import org.apache.commons.io.IOUtils; -import org.apache.log4j.Logger; +import org.apache.commons.io.input.BOMInputStream; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataReader; +import com.salesforce.dataloader.dao.AbstractDataReaderImpl; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.exception.DataAccessRowException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; import com.salesforce.dataloader.util.DAORowUtil; import com.sforce.async.CSVReader; @@ -53,53 +57,46 @@ * * @author Federico Recio */ -public class CSVFileReader implements DataReader { +public class CSVFileReader extends AbstractDataReaderImpl { - private static final Logger LOGGER = Logger.getLogger(CSVFileReader.class); + private static final Logger LOGGER = DLLogManager.getLogger(CSVFileReader.class); private final Object lock = new Object(); private File file; private FileInputStream input; - private int totalRows; private CSVReader csvReader; - private int currentRowNumber; - private boolean forceUTF8; - private List headerRow; private boolean isOpen; private char[] csvDelimiters; - - public CSVFileReader(Config config) { - this(new File(config.getString(Config.DAO_NAME)), config, true); - } - - public CSVFileReader(String filePath, Controller controller) { - this(new File(filePath), controller.getConfig(), false); - } - - public CSVFileReader(String filePath, Controller controller, boolean custDelimiter) { - this(new File(filePath), controller.getConfig(), custDelimiter); - } - - // Used only by the test - public CSVFileReader(File file, Config config) { - this(file, config, true); - } - - public CSVFileReader(File file, Config config, boolean custDelimiter) { + private boolean endOfFileReached = false; + + // Handles 3 types of CSV files: + // 1. CSV files provided by the user for upload operations: ignoreDelimiterConfig = false, isQueryOperationResult = false + // 2. CSV files that are results of query operations: ignoreDelimiterConfig = false, isQueryOperationResult = true + // 3. CSV files that capture successes/failures when performing an upload operation: ignoreDelimiterConfig = true, isQueryOperationResult = + // isQueryOperationsResult value is ignored if ignoreDelimiterConfig is 'true'. + public CSVFileReader(File file, AppConfig appConfig, boolean ignoreDelimiterConfig, boolean isQueryOperationResult) { + super(appConfig); this.file = file; - forceUTF8 = config.getBoolean(Config.READ_UTF8); StringBuilder separator = new StringBuilder(); - if (custDelimiter) { - if (config.getBoolean(Config.CSV_DELIMETER_COMMA)) { - separator.append(","); - } - if (config.getBoolean(Config.CSV_DELIMETER_TAB)) { - separator.append("\t"); - } - if (config.getBoolean(Config.CSV_DELIMETER_OTHER)) { - separator.append(config.getString(Config.CSV_DELIMETER_OTHER_VALUE)); - } + if (ignoreDelimiterConfig) { + separator.append(AppUtil.COMMA); + LOGGER.debug(Messages.getString("CSVFileDAO.debugMessageCommaSeparator")); } else { - separator.append(","); + if (isQueryOperationResult) { + separator.append(appConfig.getString(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS)); + } else { // reading CSV for a load operation + if (appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_COMMA)) { + separator.append(AppUtil.COMMA); + LOGGER.debug(Messages.getString("CSVFileDAO.debugMessageCommaSeparator")); + } + if (appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_TAB)) { + separator.append(AppUtil.TAB); + LOGGER.debug(Messages.getString("CSVFileDAO.debugMessageTabSeparator")); + } + if (appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_OTHER)) { + separator.append(appConfig.getString(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE)); + LOGGER.debug(Messages.getFormattedString("CSVFileDAO.debugMessageSeparatorChar", separator)); + } + } } csvDelimiters = separator.toString().toCharArray(); @@ -117,15 +114,8 @@ public void checkConnection() throws DataAccessObjectInitializationException { } @Override - public void open() throws DataAccessObjectInitializationException { - if (isOpen) { - close(); - } - currentRowNumber = 0; - + protected void openDAO() throws DataAccessObjectInitializationException { initalizeInput(csvDelimiters); - readHeaderRow(); - isOpen = true; } /** @@ -141,42 +131,20 @@ public void close() { isOpen = false; } } - - /** - * Checks the Bytes for the UTF-8 BOM if found, returns true, else false - */ - private boolean isUTF8File(File file) { - - FileInputStream stream = null; - - // UTF-8 BOM is 0xEE 0xBB OxBf - // or 239 187 191 - - try { - stream = new FileInputStream(file); - - if (stream.read() == 239) { - if (stream.read() == 187) { - if (stream.read() == 191) { - return true; - } - } - } - } catch (FileNotFoundException e) { - LOGGER.error("Error in file when testing CSV"); - } catch (IOException io) { - LOGGER.error("IO error when testing file"); - } finally { - IOUtils.closeQuietly(stream); - } - return false; + + protected void setOpenFlag(boolean openFlag) { + this.isOpen = openFlag; } - + + protected boolean isOpenFlag() { + return this.isOpen; + } + @Override - public List readRowList(int maxRows) throws DataAccessObjectException { - List outputRows = new ArrayList(); + protected List readTableRowListFromDAO(int maxRows) throws DataAccessObjectException { + List outputRows = new ArrayList(); for (int i = 0; i < maxRows; i++) { - Row outputRow = readRow(); + TableRow outputRow = readTableRow(); if (outputRow != null) { // if row has been returned, add it to the output outputRows.add(outputRow); @@ -193,11 +161,10 @@ public List readRowList(int maxRows) throws DataAccessObjectException { * Updates the current record number */ @Override - public Row readRow() throws DataAccessObjectException { - if (!isOpen) { - open(); + protected TableRow readTableRowFromDAO() throws DataAccessObjectException { + if (endOfFileReached) { + return null; } - List record; synchronized (lock) { try { @@ -208,74 +175,55 @@ record = csvReader.nextRecord(); } if (!DAORowUtil.isValidRow(record)) { + endOfFileReached = true; return null; } - if (record.size() > headerRow.size()) { + if (record.size() > getColumnNames().size()) { String errMsg = Messages.getFormattedString("CSVFileDAO.errorRowTooLarge", new String[]{ - String.valueOf(currentRowNumber), String.valueOf(record.size()), String.valueOf(headerRow.size())}); + String.valueOf(getCurrentRowNumber()), String.valueOf(record.size()), String.valueOf(getColumnNames().size())}); + throw new DataAccessRowException(errMsg); + } else if (record.size() < getColumnNames().size()) { + String errMsg = Messages.getFormattedString("CSVFileDAO.errorRowTooSmall", new String[]{ + String.valueOf(getCurrentRowNumber()), String.valueOf(record.size()), String.valueOf(getColumnNames().size())}); throw new DataAccessRowException(errMsg); } - Row row = new Row(record.size()); + TableRow trow = new TableRow(getTableHeader()); - for (int i = 0; i < headerRow.size(); i++) { + for (int i = 0; i < getColumnNames().size(); i++) { String value = record.get(i); if (value == null) { value = ""; } - row.put(headerRow.get(i), value); + trow.put(getColumnNames().get(i), value); } - currentRowNumber++; - return row; - } - - /** - * @return Names of output columns being read during each readRow call - */ - @Override - public List getColumnNames() { - return headerRow; + return trow; } - /* - * Returns the number of rows in the file. Side effect: Moves the row pointer to the first row - */ - @Override - public int getTotalRows() throws DataAccessObjectException { - if (totalRows == 0) { - if (!isOpen) { - throw new IllegalStateException("File is not open"); - } - totalRows = DAORowUtil.calculateTotalRows(this); - } - return totalRows; - } - - /** - * @return Current record number that has been read - */ - @Override - public int getCurrentRowNumber() { - return currentRowNumber; - } - - private void readHeaderRow() throws DataAccessObjectInitializationException { + protected List initializeDaoColumnsList() throws DataAccessObjectInitializationException { + List daoColsList = null; try { synchronized (lock) { - headerRow = csvReader.nextRecord(); + daoColsList = csvReader.nextRecord(); } - if (headerRow == null) { + if (daoColsList == null) { LOGGER.error(Messages.getString("CSVFileDAO.errorHeaderRow")); throw new DataAccessObjectInitializationException(Messages.getString("CSVFileDAO.errorHeaderRow")); } + + LOGGER.debug(Messages.getFormattedString( + "CSVFileDAO.debugMessageHeaderRowSize", daoColsList.size())); + + LOGGER.info("Columns in CSV header = " + daoColsList.size()); + return daoColsList; } catch (IOException e) { String errMsg = Messages.getString("CSVFileDAO.errorHeaderRow"); LOGGER.error(errMsg, e); throw new DataAccessObjectInitializationException(errMsg, e); } finally { // if there's a problem getting header row, the stream needs to be closed - if (headerRow == null) { + if (daoColsList == null) { IOUtils.closeQuietly(input); } } @@ -285,10 +233,26 @@ private void initalizeInput(char[] csvDelimiters) throws DataAccessObjectInitial try { input = new FileInputStream(file); - if (forceUTF8 || isUTF8File(file)) { - csvReader = new CSVReader(input, "UTF-8", csvDelimiters); + String encoding = this.getAppConfig().getCsvEncoding(false); + if (StandardCharsets.UTF_8.name().equals(encoding) + || StandardCharsets.UTF_16BE.name().equals(encoding) + || StandardCharsets.UTF_16LE.name().equals(encoding) + || "UTF-32LE".equals(encoding) + || "UTF-32BE".equals(encoding)) { + BOMInputStream bomInputStream = + BOMInputStream.builder() + .setFile(file) + .setByteOrderMarks(ByteOrderMark.UTF_8, + ByteOrderMark.UTF_16LE, + ByteOrderMark.UTF_16BE, + ByteOrderMark.UTF_32LE, + ByteOrderMark.UTF_32BE) + .setInclude(false) + .get(); + csvReader = new CSVReader(bomInputStream, encoding, csvDelimiters); } else { - csvReader = new CSVReader(input, csvDelimiters); + csvReader = new CSVReader(input, encoding, csvDelimiters); + LOGGER.debug(this.getClass().getName(), "encoding used to read from CSV file is " + encoding); } csvReader.setMaxRowsInFile(Integer.MAX_VALUE); csvReader.setMaxCharsInFile(Integer.MAX_VALUE); @@ -300,6 +264,8 @@ private void initalizeInput(char[] csvDelimiters) throws DataAccessObjectInitial String errMsg = Messages.getString("CSVFileDAO.errorUnsupportedEncoding"); LOGGER.error(errMsg, e); throw new DataAccessObjectInitializationException(errMsg, e); + } catch (IOException e) { + throw new DataAccessObjectInitializationException(e); } finally { if (csvReader == null) { IOUtils.closeQuietly(input); diff --git a/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileWriter.java b/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileWriter.java index dfb0341a3..cf494d35a 100644 --- a/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileWriter.java +++ b/src/main/java/com/salesforce/dataloader/dao/csv/CSVFileWriter.java @@ -28,20 +28,23 @@ import java.io.BufferedWriter; import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.OutputStreamWriter; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.dao.DataWriter; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.RowInterface; +import com.salesforce.dataloader.util.AppUtil; /** * Writes csv files. @@ -52,12 +55,13 @@ public class CSVFileWriter implements DataWriter { //logger - private static Logger logger = Logger.getLogger(CSVFileWriter.class); + private static Logger logger = DLLogManager.getLogger(CSVFileWriter.class); private final String fileName; private BufferedWriter fileOut; private List columnNames = new ArrayList(); private int currentRowNumber = 0; + private boolean isHeaderRowWritten = false; /** * open is true if the writer file is open, false otherwise. @@ -67,17 +71,30 @@ public class CSVFileWriter implements DataWriter { /** * encoding contains a value for output character encoding, blank indicates "use default" */ - private final String encoding; + private String encoding; /** * If capitalizedHeadings is true, output header row in caps */ - private final boolean capitalizedHeadings; + private boolean capitalizedHeadings = false; + private final char columnDelimiter; + private AppConfig appConfig; + + public CSVFileWriter(String fileName, AppConfig appConfig, String columnDelimiterStr) { - public CSVFileWriter(String fileName, Config config) { this.fileName = fileName; - this.capitalizedHeadings = true; - encoding = config.getCsvWriteEncoding(); + this.appConfig = appConfig; + encoding = appConfig.getCsvEncoding(true); + logger.debug("CSV encoding is set to " + Charset.forName(encoding)); + if (encoding == null) { + encoding = Charset.defaultCharset().name(); + } + logger.debug(this.getClass().getName(), "encoding used to write to CSV file is " + encoding); + if (columnDelimiterStr.length() == 0) { + columnDelimiterStr = AppUtil.COMMA; + } + this.columnDelimiter = columnDelimiterStr.charAt(0); + this.capitalizedHeadings = appConfig.getOperationInfo().isExtraction() && appConfig.getBoolean(AppConfig.PROP_EXTRACT_ALL_CAPS_HEADERS); } /** @@ -97,12 +114,13 @@ public void checkConnection() throws DataAccessObjectInitializationException { @Override public void open() throws DataAccessObjectInitializationException { try { - if (this.encoding != null) { - fileOut = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(this.fileName), this.encoding)); - } else { - fileOut = new BufferedWriter(new FileWriter(this.fileName)); - } + FileOutputStream os = new FileOutputStream(this.fileName); + OutputStreamWriter osw = new OutputStreamWriter(os, this.encoding); + fileOut = new BufferedWriter(osw); currentRowNumber = 0; + if (appConfig.getBoolean(AppConfig.PROP_EXTRACT_CSV_OUTPUT_BOM)) { + os.write(getBOM()); + } setOpen(true); } catch (IOException e) { String errMsg = Messages.getFormattedString("CSVWriter.errorOpening", this.fileName); @@ -110,6 +128,16 @@ public void open() throws DataAccessObjectInitializationException { throw new DataAccessObjectInitializationException(errMsg, e); } } + + private byte[] getBOM() { + if (StandardCharsets.UTF_8.equals(Charset.forName(this.encoding))) { + return new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; + } else if (this.encoding.startsWith(StandardCharsets.UTF_16.name()) + || this.encoding.startsWith("UTF-32")) { + return new byte[]{(byte) 0xFE, (byte) 0xFF}; + } + return new byte[0]; + } /* * (non-Javadoc) @@ -120,7 +148,16 @@ public void close() { if (fileOut != null) { try { + if (!isHeaderRowWritten) { + try { + writeHeaderRow(); + } catch (DataAccessObjectInitializationException e) { + logger.warn("Unable to write header row in the file " + fileName); + } + } + this.isHeaderRowWritten = false; fileOut.close(); + fileOut = null; } catch (IOException e) { logger.error(Messages.getString("CSVWriter.errorClosing"), e); //$NON-NLS-1$ } @@ -129,13 +166,21 @@ public void close() { } } } + + public String getFileName() { + return this.fileName; + } private void writeHeaderRow() throws DataAccessObjectInitializationException { - CSVColumnVisitor visitor = new CSVColumnVisitor(fileOut); + if (this.isHeaderRowWritten) { + return; + } + CSVColumnVisitor visitor = new CSVColumnVisitor(fileOut, false, this.columnDelimiter); try { visitHeaderColumns(this.columnNames, visitor); fileOut.newLine(); visitor.newRow(); + this.isHeaderRowWritten = true; } catch (IOException e) { String errMsg = Messages.getString("CSVWriter.errorWriting"); logger.error(errMsg, e); @@ -148,8 +193,12 @@ private void writeHeaderRow() throws DataAccessObjectInitializationException { * @see com.salesforce.dataloader.dao.csv.Writer#writeRow(java.util.Map) */ @Override - public boolean writeRow(Row row) throws DataAccessObjectException { - CSVColumnVisitor visitor = new CSVColumnVisitor(fileOut); + public boolean writeRow(RowInterface row) throws DataAccessObjectException { + if (this.columnNames == null || this.columnNames.isEmpty()) { + ListcolNames = row.getColumnNames(); + this.setColumnNames(colNames); + } + CSVColumnVisitor visitor = new CSVColumnVisitor(fileOut, false, this.columnDelimiter); try { visitColumns(columnNames, row, visitor); fileOut.newLine(); @@ -167,15 +216,15 @@ public boolean writeRow(Row row) throws DataAccessObjectException { * @see com.salesforce.dataloader.dao.csv.Writer#writeRowList(java.util.List) */ @Override - public boolean writeRowList(List rows) throws DataAccessObjectException { + public boolean writeRowList(List rows) throws DataAccessObjectException { boolean success = true; // return the last result, should be same as others - for (Row row : rows) { + for (RowInterface row : rows) { success = writeRow(row); } return success; } - + private void visitHeaderColumns(List columnNames, CSVColumnVisitor visitor) throws IOException { for (String colName : columnNames) { String outColName; @@ -192,9 +241,15 @@ private void visitHeaderColumns(List columnNames, CSVColumnVisitor visit } } - static private void visitColumns(List columnNames, Row row, CSVColumnVisitor visitor) throws IOException { + static private void visitColumns(List columnNames, RowInterface row, CSVColumnVisitor visitor) throws IOException { for (String colName : columnNames) { Object colVal = row.get(colName); + if (colVal == null && colName.contains("(")) { + int lparenIdx = colName.indexOf('('); + int rparenIdx = colName.indexOf(')'); + colName = colName.substring(lparenIdx + 1, rparenIdx); + colVal = row.get(colName); + } visitor.visit(colVal != null ? colVal.toString() : ""); } } @@ -237,5 +292,4 @@ public void setOpen(boolean open) { public int getCurrentRowNumber() { return currentRowNumber; } - } diff --git a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseConfig.java b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseConfig.java index a583c0fe0..f721c1261 100644 --- a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseConfig.java +++ b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseConfig.java @@ -25,9 +25,9 @@ */ package com.salesforce.dataloader.dao.database; -import org.apache.commons.dbcp.BasicDataSource; -import org.springframework.beans.factory.xml.XmlBeanFactory; -import org.springframework.core.io.FileSystemResource; +import org.apache.commons.dbcp2.BasicDataSource; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; public class DatabaseConfig { private BasicDataSource dataSource; @@ -44,7 +44,15 @@ public DatabaseConfig() { * @return instance of database configuration */ public static DatabaseConfig getInstance (String dbConfigFilename, String dbConnectionName) { - XmlBeanFactory configFactory = new XmlBeanFactory(new FileSystemResource(dbConfigFilename)); + String dbConfigFileLocation = dbConfigFilename; + + //don't modify window local file system paths or URIs + if (!dbConfigFileLocation.contains(":")){ + dbConfigFileLocation = "file://".concat(dbConfigFileLocation); + } + + @SuppressWarnings("resource") + ApplicationContext configFactory = new FileSystemXmlApplicationContext(dbConfigFileLocation); return (DatabaseConfig)configFactory.getBean(dbConnectionName); } diff --git a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseContext.java b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseContext.java index 29f68de97..78ee56a62 100644 --- a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseContext.java +++ b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseContext.java @@ -31,13 +31,16 @@ import javax.sql.DataSource; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.dyna.DateConverter; +import com.salesforce.dataloader.dyna.DateTimeConverter; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.RowInterface; /** * Describe your class here. @@ -57,7 +60,7 @@ public class DatabaseContext { public static final String DEFAULT_CONFIG_FILENAME = "database-conf.xml"; // logger - private static Logger logger = Logger.getLogger(DatabaseContext.class); + private static Logger logger = DLLogManager.getLogger(DatabaseContext.class); public DatabaseContext(String dbConfigName) { this.dbConfigName = dbConfigName; @@ -147,25 +150,25 @@ public void replaceSqlParams(String sqlString) { * Values for the parameter replacement * @throws ParameterLoadException */ - public void setSqlParamValues(SqlConfig sqlConfig, Config config, Map paramValues) + public void setSqlParamValues(SqlConfig sqlConfig, AppConfig appConfig, RowInterface paramValues) throws ParameterLoadException { // detect if there're no parameters to set if (sqlConfig.getSqlParams() == null) { return; } if (paramValues == null) { - paramValues = new HashMap(); + paramValues = new Row(); } for (String paramName : sqlConfig.getSqlParams().keySet()) { String type = sqlConfig.getSqlParams().get(paramName); - if (paramValues.containsKey(paramName)) { - Object sqlValue = mapParamToDbType(config, paramValues.get(paramName), type); + if (paramValues.getColumnNames().contains(paramName)) { + Object sqlValue = mapParamToDbType(appConfig, paramValues.get(paramName), type); paramValues.put(paramName, sqlValue); } else { // look in the config if the parameter value is not passed in - if (config.contains(paramName)) { - Object configValue = getConfigValue(config, paramName, type); - Object sqlValue = mapParamToDbType(config, configValue, type); + if (appConfig.contains(paramName)) { + Object configValue = getConfigValue(appConfig, paramName, type); + Object sqlValue = mapParamToDbType(appConfig, configValue, type); logger.info(Messages.getFormattedString("DatabaseDAO.sqlParamInfo", new String[] { paramName, sqlValue.toString() })); paramValues.put(paramName, sqlValue); @@ -219,24 +222,24 @@ private int getSqlType(String type) { throw new UnsupportedOperationException("Type not supported: " + type); } - private Object getConfigValue(Config config, String paramName, String type) throws ParameterLoadException { + private Object getConfigValue(AppConfig appConfig, String paramName, String type) throws ParameterLoadException { Object value; try { if (type.equals(java.sql.Date.class.getName()) || type.equals(Timestamp.class.getName()) || type.equals(Time.class.getName())) { - value = config.getDate(paramName); + value = appConfig.getDate(paramName); } else if (type.equals(boolean.class.getName())) { - value = config.getBoolean(paramName); + value = appConfig.getBoolean(paramName); } else if (type.equals(int.class.getName())) { - value = config.getInt(paramName); + value = appConfig.getInt(paramName); } else if (type.equals(long.class.getName())) { - value = config.getLong(paramName); + value = appConfig.getLong(paramName); } else if (type.equals(float.class.getName())) { - value = config.getFloat(paramName); + value = appConfig.getFloat(paramName); } else if (type.equals(double.class.getName())) { - value = config.getDouble(paramName); + value = appConfig.getDouble(paramName); } else { - value = config.getString(paramName); + value = appConfig.getString(paramName); } } catch (ParameterLoadException e) { String errMsg = Messages.getFormattedString("DatabaseDAO.errorSqlParamReplace", new String[] { paramName, @@ -250,13 +253,13 @@ private Object getConfigValue(Config config, String paramName, String type) thro /** * Map Sql replacement parameters from config file values to an object usable as a replacement in a Sql statement * - * @param config + * @param appConfig * @param paramName * @param type * @return An object of type usable as a replacement in a Sql statement * @throws ParameterLoadException */ - private Object mapParamToDbType(Config cfg, Object paramValue, String type) throws ParameterLoadException { + private Object mapParamToDbType(AppConfig cfg, Object paramValue, String type) throws ParameterLoadException { Object sqlValue; if(paramValue == null) { return paramValue; @@ -288,7 +291,7 @@ private long getTimeInMillis(TimeZone tz, Object paramValue) { return ((Date)paramValue).getTime(); } else if(paramValue instanceof String) { - Calendar cal = (Calendar)new DateConverter(tz).convert(java.util.Calendar.class, paramValue); + Calendar cal = (Calendar)new DateTimeConverter(tz, false).convert(java.util.Calendar.class, paramValue); return cal.getTimeInMillis(); } else { throw new IllegalArgumentException(Messages.getFormattedString("DatabaseDAO.errorParamMappingType", paramValue.getClass().getName())); diff --git a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseReader.java b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseReader.java index 0b3b83788..9808d6e3c 100644 --- a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseReader.java +++ b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseReader.java @@ -29,15 +29,17 @@ import java.sql.*; import java.util.*; -import com.salesforce.dataloader.model.Row; -import org.apache.commons.dbcp.BasicDataSource; -import org.apache.log4j.Logger; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; -import com.salesforce.dataloader.config.Config; +import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.dao.DataReader; +import com.salesforce.dataloader.dao.AbstractDataReaderImpl; import com.salesforce.dataloader.exception.*; -import com.salesforce.dataloader.util.DAORowUtil; /** * Data Access Object (DAO) that connects to database to update and retrieve the data This is a generic data access @@ -47,26 +49,23 @@ * * @author Alex Warshavsky */ -public class DatabaseReader implements DataReader { +public class DatabaseReader extends AbstractDataReaderImpl { // logger - private static Logger logger = Logger.getLogger(DatabaseReader.class); + private static Logger logger = DLLogManager.getLogger(DatabaseReader.class); private final BasicDataSource dataSource; - private final Config config; - private List columnNames = new ArrayList(); - private int totalRows = 0; - private int currentRowNumber = 0; private final SqlConfig sqlConfig; private final DatabaseContext dbContext; + private boolean endOfTableReached = false; /** * Get an instance of database reader for the data access object name from configuration - * @param config + * @param appConfig * @throws DataAccessObjectInitializationException */ - public DatabaseReader(Config config) throws DataAccessObjectInitializationException { - this(config, config.getString(Config.DAO_NAME)); + public DatabaseReader(AppConfig appConfig) throws DataAccessObjectInitializationException { + this(appConfig, appConfig.getString(AppConfig.PROP_DAO_NAME)); } /** @@ -74,9 +73,9 @@ public DatabaseReader(Config config) throws DataAccessObjectInitializationExcept * @param dbConfigName * @throws DataAccessObjectInitializationException */ - public DatabaseReader(Config config, String dbConfigName) throws DataAccessObjectInitializationException { - this.config = config; - String dbConfigFilename = config.constructConfigFilePath(DatabaseContext.DEFAULT_CONFIG_FILENAME); + public DatabaseReader(AppConfig appConfig, String dbConfigName) throws DataAccessObjectInitializationException { + super(appConfig); + String dbConfigFilename = appConfig.constructConfigFilePath(DatabaseContext.DEFAULT_CONFIG_FILENAME); if(! (new File(dbConfigFilename).exists())) { throw new DataAccessObjectInitializationException(Messages.getFormattedString("DatabaseDAO.errorConfigFileExists", dbConfigFilename)); //$NON-NLS-1$ } @@ -84,10 +83,6 @@ public DatabaseReader(Config config, String dbConfigName) throws DataAccessObjec this.dataSource = dbConfig.getDataSource(); this.sqlConfig = dbConfig.getSqlConfig(); this.dbContext = new DatabaseContext(dbConfigName); - this.columnNames = sqlConfig.getColumnNames(); - if(columnNames == null) { - columnNames = new ArrayList(); - } } /* @@ -95,7 +90,7 @@ public DatabaseReader(Config config, String dbConfigName) throws DataAccessObjec * @see com.salesforce.dataloader.dao.DataAccessObject#open() */ @Override - public void open() throws DataAccessObjectInitializationException { + protected void openDAO() throws DataAccessObjectInitializationException { open(null); } @@ -105,8 +100,7 @@ public void open() throws DataAccessObjectInitializationException { * @param params * @throws DataAccessObjectInitializationException */ - public void open(Map params) throws DataAccessObjectInitializationException { - currentRowNumber = 0; + private void open(Map params) throws DataAccessObjectInitializationException { try { setupQuery(params); } catch (DataAccessObjectInitializationException e) { @@ -114,7 +108,14 @@ public void open(Map params) throws DataAccessObjectInitializatio } catch (Exception e) { throw new DataAccessObjectInitializationException(e.getMessage(), e); } - dbContext.setOpen(true); + } + + protected void setOpenFlag(boolean open) { + dbContext.setOpen(open); + } + + protected boolean isOpenFlag() { + return dbContext.isOpen(); } private void setupQuery(Map params) throws DataAccessObjectInitializationException, ParameterLoadException, IllegalArgumentException { @@ -124,20 +125,28 @@ private void setupQuery(Map params) throws DataAccessObjectInitia PreparedStatement statement = dbContext.prepareStatement(); // right now, query doesn't support data input -- all the parameters are static vs. update which takes data // for every put call - dbContext.setSqlParamValues(sqlConfig, config, params); + TableRow row = null; + if (params != null) { + Set colHeaderNames = params.keySet(); + @SuppressWarnings("unchecked") + TableHeader header = new TableHeader((List)(Object)Arrays.asList(colHeaderNames.toArray())); + row = new TableRow(header); + } + dbContext.setSqlParamValues(sqlConfig, + this.getAppConfig(), row); // set the query fetch size int fetchSize; try { - fetchSize = config.getInt(Config.DAO_READ_BATCH_SIZE); - if(fetchSize > Config.MAX_DAO_READ_BATCH_SIZE) { - fetchSize = Config.MAX_DAO_READ_BATCH_SIZE; + fetchSize = this.getAppConfig().getInt(AppConfig.PROP_DAO_READ_BATCH_SIZE); + if(fetchSize > AppConfig.MAX_DAO_READ_BATCH_SIZE) { + fetchSize = AppConfig.MAX_DAO_READ_BATCH_SIZE; } } catch (ParameterLoadException e) { // warn about getting batch size parameter, otherwise continue w/ default logger.warn(Messages.getFormattedString("DatabaseDAO.errorGettingBatchSize", new String[] { - String.valueOf(Config.DEFAULT_DAO_READ_BATCH_SIZE), e.getMessage() })); - fetchSize = Config.DEFAULT_DAO_READ_BATCH_SIZE; + String.valueOf(AppConfig.DEFAULT_DAO_READ_BATCH_SIZE), e.getMessage() })); + fetchSize = AppConfig.DEFAULT_DAO_READ_BATCH_SIZE; } statement.setFetchSize(fetchSize); @@ -150,16 +159,16 @@ private void setupQuery(Map params) throws DataAccessObjectInitia throw new DataAccessObjectInitializationException(errMsg, sqe); } } - + /* * (non-Javadoc) - * @see com.salesforce.dataloader.dao.DataReader#readRowList(int) + * @see com.salesforce.dataloader.dao.DataReader#readTableRowList(int) */ @Override - public List readRowList(int maxRows) throws DataAccessObjectException { - List outputRows = new ArrayList(); + protected List readTableRowListFromDAO(int maxRows) throws DataAccessObjectException { + List outputRows = new ArrayList(); for(int i=0; i < maxRows; i++) { - Row outputRow = readRow(); + TableRow outputRow = readTableRow(); if(outputRow != null) { // if row has been returned, add it to the output outputRows.add(outputRow); @@ -170,71 +179,44 @@ public List readRowList(int maxRows) throws DataAccessObjectException { } return outputRows; } - @Override - public Row readRow() throws DataAccessObjectException { - Row row = null; - - if (!dbContext.isOpen()) { - open(); + protected TableRow readTableRowFromDAO() throws DataAccessObjectException { + if (endOfTableReached) { + return null; } - String currentColumnName = ""; try { + TableRow trow = null; ResultSet rs = dbContext.getDataResultSet(); if (rs != null && rs.next()) { - row = new Row(columnNames.size()); + trow = new TableRow(getTableHeader()); - for (String columnName : columnNames) { + for (String columnName : getColumnNames()) { currentColumnName = columnName; Object value = rs.getObject(columnName); - row.put(columnName, value); + trow.put(columnName, value); } - currentRowNumber++; } - return row; + if (trow == null) { + endOfTableReached = true; + return null; + } + return trow; } catch (SQLException sqe) { String errMsg = Messages.getFormattedString("DatabaseDAO.sqlExceptionReadRow", new String[] { - currentColumnName, String.valueOf(currentRowNumber + 1), dbContext.getDbConfigName(), sqe.getMessage() }); + currentColumnName, String.valueOf(getCurrentRowNumber() + 1), dbContext.getDbConfigName(), sqe.getMessage() }); logger.error(errMsg, sqe); close(); throw new DataAccessObjectException(errMsg, sqe); } catch (Exception e) { String errMsg = Messages.getFormattedString("DatabaseDAO.exceptionReadRow", new String[] { - currentColumnName, String.valueOf(currentRowNumber + 1), dbContext.getDbConfigName(), e.getMessage() }); + currentColumnName, String.valueOf(getCurrentRowNumber() + 1), dbContext.getDbConfigName(), e.getMessage() }); logger.error(errMsg, e); close(); throw new DataAccessObjectException(errMsg, e); } } - @Override - public int getTotalRows() throws DataAccessObjectException { - boolean skipRowCount = Config.DEFAULT_SKIP_TOTAL_COUNT; - - if (config.contains(Config.DAO_SKIP_TOTAL_COUNT)) - skipRowCount = config.getBoolean(Config.DAO_SKIP_TOTAL_COUNT); - - if (skipRowCount == true) - return 0; - - if (totalRows == 0) { - totalRows = DAORowUtil.calculateTotalRows(this); - } - - return totalRows; - } - - @Override - public int getCurrentRowNumber() { - return currentRowNumber; - } - - @Override - public List getColumnNames() { - return columnNames; - } - /* * (non-Javadoc) * @see com.salesforce.dataloader.dao.DataAccessObject#checkConnection() @@ -252,4 +234,20 @@ public void checkConnection() throws DataAccessObjectInitializationException { public void close() { dbContext.close(); } + + public int getTotalRows() throws DataAccessObjectException { + if (this.getAppConfig().getBoolean(AppConfig.PROP_DAO_SKIP_TOTAL_COUNT)) { + return 0; + } + return super.getTotalRows(); + } + + @Override + protected List initializeDaoColumnsList() { + List daoColsList = sqlConfig.getColumnNames(); + if(daoColsList == null) { + daoColsList = new ArrayList(); + } + return daoColsList; + } } diff --git a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseWriter.java b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseWriter.java index 12791c245..aab1f1ea6 100644 --- a/src/main/java/com/salesforce/dataloader/dao/database/DatabaseWriter.java +++ b/src/main/java/com/salesforce/dataloader/dao/database/DatabaseWriter.java @@ -29,11 +29,12 @@ import java.sql.*; import java.util.*; -import com.salesforce.dataloader.model.Row; -import org.apache.commons.dbcp.BasicDataSource; -import org.apache.log4j.Logger; +import com.salesforce.dataloader.model.RowInterface; +import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.dao.DataWriter; import com.salesforce.dataloader.exception.*; @@ -47,28 +48,28 @@ public class DatabaseWriter implements DataWriter { // logger - private static Logger logger = Logger.getLogger(DatabaseReader.class); + private static Logger logger = DLLogManager.getLogger(DatabaseReader.class); private final BasicDataSource dataSource; - private final Config config; + private final AppConfig appConfig; private int currentRowNumber = 0; private final SqlConfig sqlConfig; private final DatabaseContext dbContext; - public DatabaseWriter(Config config) throws DataAccessObjectInitializationException { - this(config, config.getString(Config.DAO_NAME)); + public DatabaseWriter(AppConfig appConfig) throws DataAccessObjectInitializationException { + this(appConfig, appConfig.getString(AppConfig.PROP_DAO_NAME)); } /** * Create database writer based on configuration * - * @param config + * @param appConfig * @param dbConfigName * @throws DataAccessObjectInitializationException */ - DatabaseWriter(Config config, String dbConfigName) throws DataAccessObjectInitializationException { - this.config = config; - String dbConfigFilename = config.constructConfigFilePath(DatabaseContext.DEFAULT_CONFIG_FILENAME); + DatabaseWriter(AppConfig appConfig, String dbConfigName) throws DataAccessObjectInitializationException { + this.appConfig = appConfig; + String dbConfigFilename = appConfig.constructConfigFilePath(DatabaseContext.DEFAULT_CONFIG_FILENAME); if (!(new File(dbConfigFilename).exists())) { throw new DataAccessObjectInitializationException( Messages.getFormattedString("DatabaseDAO.errorConfigFileExists", dbConfigFilename)); //$NON-NLS-1$ } @@ -78,8 +79,8 @@ public DatabaseWriter(Config config) throws DataAccessObjectInitializationExcept dbContext = new DatabaseContext(dbConfigName); } - DatabaseWriter(Config config, String dbConfigName, BasicDataSource dataSource, SqlConfig sqlConfig) { - this.config = config; + DatabaseWriter(AppConfig appConfig, String dbConfigName, BasicDataSource dataSource, SqlConfig sqlConfig) { + this.appConfig = appConfig; this.dataSource = dataSource; this.sqlConfig = sqlConfig; this.dbContext = new DatabaseContext(dbConfigName); @@ -118,13 +119,13 @@ private void setupUpdate() throws DataAccessObjectInitializationException { dbContext.replaceSqlParams(sqlConfig.getSqlString()); dbContext.prepareStatement(); } - + /* * (non-Javadoc) * @see com.salesforce.dataloader.dao.DataWriter#writeRowList(java.util.List) */ @Override - public boolean writeRowList(List inputRowList) throws DataAccessObjectException { + public boolean writeRowList(List inputRowList) throws DataAccessObjectException { // make sure that the update is setup and ready to go, otherwise stop if (!dbContext.isOpen()) { throw new DataAccessObjectInitializationException(Messages @@ -136,13 +137,13 @@ public boolean writeRowList(List inputRowList) throws DataAccessObjectExcep try { //for batchsize = 1, don't do batching, this provides much better error output if(inputRowList.size() == 1) { - dbContext.setSqlParamValues(sqlConfig, config, inputRowList.get(0)); + dbContext.setSqlParamValues(sqlConfig, appConfig, inputRowList.get(0)); currentRowNumber++; } else { // for each row set the Sql params in the prepared statement dbContext.getDataStatement().clearBatch(); - for (Row inputRow : inputRowList) { - dbContext.setSqlParamValues(sqlConfig, config, inputRow); + for (RowInterface inputRow : inputRowList) { + dbContext.setSqlParamValues(sqlConfig, appConfig, inputRow); dbContext.getDataStatement().addBatch(); currentRowNumber++; } @@ -231,13 +232,13 @@ public boolean writeRowList(List inputRowList) throws DataAccessObjectExcep * @throws DataAccessObjectException */ @Override - public boolean writeRow(Row inputRow) throws DataAccessObjectException { + public boolean writeRow(RowInterface inputRow) throws DataAccessObjectException { // FIXME: Think about refactoring this for the caller to writeRow() and here take care of batching internally - List inputRowList = new ArrayList(); + List inputRowList = new ArrayList(); inputRowList.add(inputRow); return writeRowList(inputRowList); } - + /** * @param sqe */ diff --git a/src/main/java/com/salesforce/dataloader/dyna/BooleanConverter.java b/src/main/java/com/salesforce/dataloader/dyna/BooleanConverter.java index 782779225..be3797e29 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/BooleanConverter.java +++ b/src/main/java/com/salesforce/dataloader/dyna/BooleanConverter.java @@ -45,34 +45,8 @@ public final class BooleanConverter implements Converter { public BooleanConverter() { - this.defaultValue = null; - this.useDefault = false; - - } - - public BooleanConverter(Object defaultValue) { - - this.defaultValue = defaultValue; - this.useDefault = true; - } - - // ----------------------------------------------------- Instance Variables - - - /** - * The default value specified to our Constructor, if any. - */ - private Object defaultValue = null; - - - /** - * Should we return the default value on conversion errors? - */ - private boolean useDefault = true; - - // --------------------------------------------------------- Public Methods @@ -87,18 +61,19 @@ public BooleanConverter(Object defaultValue) { * successfully */ @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object convert(Class type, Object value) { - if (value == null || String.valueOf(value).length()==0) { - return null; + if (value == null || value instanceof Boolean) { + return value; } - - if (value instanceof Boolean) { - return (value); + + String stringValue = value.toString().trim(); + if (stringValue.length()==0) { + return null; } try { - String stringValue = value.toString(); if (stringValue.equalsIgnoreCase("yes") || stringValue.equalsIgnoreCase("y") || stringValue.equalsIgnoreCase("true") || @@ -111,17 +86,11 @@ public Object convert(Class type, Object value) { stringValue.equalsIgnoreCase("off") || stringValue.equalsIgnoreCase("0")) { return (Boolean.FALSE); - } else if (useDefault) { - return (defaultValue); } else { throw new ConversionException(stringValue); } } catch (ClassCastException e) { - if (useDefault) { - return (defaultValue); - } else { - throw new ConversionException(e); - } + throw new ConversionException(e); } } diff --git a/src/main/java/com/salesforce/dataloader/dyna/DateConverter.java b/src/main/java/com/salesforce/dataloader/dyna/DateConverter.java deleted file mode 100644 index 6d6948c2f..000000000 --- a/src/main/java/com/salesforce/dataloader/dyna/DateConverter.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.dyna; - -import java.text.*; -import java.util.*; - -import com.salesforce.dataloader.model.NACalendarValue; -import com.salesforce.dataloader.model.NATextValue; -import org.apache.commons.beanutils.ConversionException; -import org.apache.commons.beanutils.Converter; -import org.apache.log4j.Logger; - -public final class DateConverter implements Converter { - - private static final TimeZone GMT_TZ = TimeZone.getTimeZone("GMT"); - private static final List supportedEuropeanPatterns = getSupportedPatterns(true); - private static final List supportedRegularPatterns = getSupportedPatterns(false); - - static Logger logger = Logger.getLogger(DateConverter.class); - /** - * The default value specified to our Constructor, if any. - */ - private final Object defaultValue; - - /** - * Should we return the default value on conversion errors? - */ - private final boolean useDefault; - private final boolean useEuroDates; - private final TimeZone timeZone; - - - - public DateConverter(TimeZone tz) { - this(tz, null, false, false); - - } - - public DateConverter(TimeZone tz, boolean useEuroDateFormat) { - this(tz, null, useEuroDateFormat, false); - } - - public DateConverter(TimeZone tz, Object defaultValue, boolean useEuroDateFormat) { - this(tz, defaultValue, useEuroDateFormat, true); - } - - private DateConverter(TimeZone tz, Object defaultValue, boolean useEuroDateFormat, boolean useDefault) { - this.timeZone = tz; - this.defaultValue = defaultValue; - this.useDefault = useDefault; - this.useEuroDates = useEuroDateFormat; - } - - public DateConverter(TimeZone tz, Object defaultValue) { - this(tz, defaultValue, false, true); - } - - private Calendar parseDate(TimeZone tz, String dateString, String pattern) { - final DateFormat df = new SimpleDateFormat(pattern); - df.setTimeZone(tz); - return parseDate(dateString, df); - } - - private Calendar parseDate(String dateString, DateFormat fmt) { - final ParsePosition pos = new ParsePosition(0); - fmt.setLenient(false); - final Date date = fmt.parse(dateString, pos); - // we only want to use the date if parsing succeeded and used the entire string - if (date != null && pos.getIndex() == dateString.length()) { - Calendar cal = Calendar.getInstance(timeZone); - cal.setTime(date); - return cal; - } - return null; - } - - /** - * Attempts to parse a date string using the given formatting patterns - * - * @param dateString - * The date string to parse - * @param patterns - * Patterns to try. These will be used in the constructor for SimpleDateFormat - * @return A Calendar object representing the given date string - */ - private Calendar tryParse(TimeZone tz, String dateString, String... patterns) { - if (patterns == null) return null; - for (String pattern : patterns) { - Calendar cal = parseDate(tz, dateString, pattern); - if (cal != null) return cal; - } - return null; - } - - - @Override - public Object convert(Class type, Object value) { - if (value == null) { - return null; - } - - if(value instanceof NATextValue) { - return NACalendarValue.getInstance(); - } - - Calendar cal = Calendar.getInstance(this.timeZone); - - if (value instanceof Date) { - cal.setTime((Date)value); - return cal; - } - - if (value instanceof Calendar) { return value; } - - String dateString = value.toString().trim(); - int len = dateString.length(); - - if (len == 0) return null; - - String gmtDateString = null; - if ("z".equalsIgnoreCase(dateString.substring(len - 1))) - gmtDateString = dateString.substring(0, len - 1); - - for (String basePattern : useEuroDates ? supportedEuropeanPatterns : supportedRegularPatterns) { - if (gmtDateString != null) - cal = tryParse(GMT_TZ, gmtDateString, basePattern); - else - cal = tryParse(this.timeZone, dateString, basePattern, basePattern + "'Z'Z", basePattern + "'z'Z", - basePattern + "z"); - if (cal != null) return cal; - } - - // FIXME -- BUG: this format is picked up as a mistake instead of MM-dd-yyyy or dd-MM-yyyy - cal = parseDate(this.timeZone, dateString, "yyyy-MM-dd"); - if (cal != null) return cal; - - if (useEuroDates) { - cal = tryParse(this.timeZone, dateString, "dd/MM/yyyy HH:mm:ss", "dd/MM/yyyy"); - - // FIXME -- Warning: this never gets picked up because of yyyy-MM-dd - /* - * Calendar cal = parseDate("dd-MM-yyyy", dateString); if (cal != null) return cal; - */ - } else { - cal = tryParse(this.timeZone, dateString, "MM/dd/yyyy HH:mm:ss", "MM/dd/yyyy"); - - //FIXME -- Warning: this never gets picked up because of yyyy-MM-dd - /* - * Calendar cal = parseDate("MM-dd-yyyy", dateString); if (cal != null) return cal; - */ - } - - if (cal != null) return cal; - - DateFormat df = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT); - df.setTimeZone(this.timeZone); - cal = parseDate(dateString, df); - if (cal != null) return cal; - - df = DateFormat.getDateInstance(DateFormat.SHORT); - df.setTimeZone(this.timeZone); - cal = parseDate(dateString, df); - if (cal != null) return cal; - - if (useDefault) { - return defaultValue; - } else { - throw new ConversionException("Failed to parse date: " + value); - } - } - - /* Helper function to produce all the patterns that DL supports */ - private static List getSupportedPatterns(boolean europeanDates) { - - List basePatterns = new ArrayList(); - - // Extended patterns means using the - delimiter in the date - - List extendedPatterns = new ArrayList(); - extendedPatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS"); - extendedPatterns.add("yyyy-MM-dd'T'HH:mm:ss"); - extendedPatterns.add("yyyy-MM-dd'T'HH:mm"); - extendedPatterns.add("yyyy-MM-dd'T'HH"); - extendedPatterns.add("yyyy-MM-dd'T'"); //? - - //As per ISO 8601 5.2.1.1, when only the days are omitted, a - is necessary between year and month - List extendedPatternsDateOnly = new ArrayList(); - extendedPatternsDateOnly.add("yyyy-MM"); - extendedPatternsDateOnly.add("yyyyMMdd"); - extendedPatternsDateOnly.add("yyyy"); - - // Using a space instead of 'T' to separate date and time - List extendedPatternsWithoutT = new ArrayList(); - extendedPatternsWithoutT.add("yyyy-MM-dd HH:mm:ss.SSS"); - extendedPatternsWithoutT.add("yyyy-MM-dd HH:mm:ss"); - extendedPatternsWithoutT.add("yyyy-MM-dd HH:mm"); - extendedPatternsWithoutT.add("yyyy-MM-dd HH"); - - // Not using anything to deliminate the date elements from each - // other. Matched through known lengths of components. - List basicPatterns = new ArrayList(); - basicPatterns.add("yyyyMMdd'T'HH:mm:ss.SSS"); - basicPatterns.add("yyyyMMdd'T'HH:mm:ss"); - basicPatterns.add("yyyyMMdd'T'HH:mm"); - basicPatterns.add("yyyyMMdd'T'HH"); - basicPatterns.add("yyyyMMdd'T'"); //? - - // Using a space instead of 'T' to separate date and time - List basicPatternsWithoutT = new ArrayList(); - basicPatternsWithoutT.add("yyyyMMdd HH:mm:ss.SSS"); - basicPatternsWithoutT.add("yyyyMMdd HH:mm:ss"); - basicPatternsWithoutT.add("yyyyMMdd HH:mm"); - basicPatternsWithoutT.add("yyyyMMdd HH"); - - //as per the iso 8601 spec - List fullBasicFormats = new ArrayList(); - fullBasicFormats.add("yyyyMMdd'T'HHmmss"); - fullBasicFormats.add("yyyyMMdd'T'HHmm"); - fullBasicFormats.add("yyyyMMdd'T'HH"); - - - List fullBasicFormatsWithoutT = new ArrayList(); - fullBasicFormatsWithoutT.add("yyyyMMdd HHmmss"); - fullBasicFormatsWithoutT.add("yyyyMMdd HHmm"); - fullBasicFormatsWithoutT.add("yyyyMMdd HH"); - - - String baseDate = europeanDates ? "dd/MM/yyyy" : "MM/dd/yyyy"; - - // Using a space instead of 'T' to separate date and time - List slashPatternsWithoutT = new ArrayList(); - extendedPatternsWithoutT.add(baseDate +" HH:mm:ss.SSS"); - extendedPatternsWithoutT.add(baseDate +" HH:mm:ss"); - extendedPatternsWithoutT.add(baseDate +" HH:mm"); - extendedPatternsWithoutT.add(baseDate +" HH"); - extendedPatternsWithoutT.add(baseDate +" HHZ"); - - List slashPatternsWithT = new ArrayList(); - extendedPatternsWithoutT.add(baseDate + "'T'HH:mm:ss.SSS"); - extendedPatternsWithoutT.add(baseDate + "'T'HH:mm:ss"); - extendedPatternsWithoutT.add(baseDate + "'T'HH:mm"); - extendedPatternsWithoutT.add(baseDate + "'T'HH"); - - //order is important here because if it matches against the wrong format first, it will - //misinterpret the time - - basePatterns.addAll(fullBasicFormatsWithoutT); - basePatterns.addAll(fullBasicFormats); - basePatterns.addAll(basicPatterns); - basePatterns.addAll(basicPatternsWithoutT); - basePatterns.addAll(extendedPatternsDateOnly); - basePatterns.addAll(extendedPatterns); - basePatterns.addAll(extendedPatternsWithoutT); - basePatterns.addAll(slashPatternsWithoutT); - basePatterns.addAll(slashPatternsWithT); - - List timeZones = new ArrayList<>(); - basePatterns.forEach(p -> timeZones.add(p + "Z")); - basePatterns.addAll(timeZones); - - return basePatterns; - } -} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/dyna/DateOnlyConverter.java b/src/main/java/com/salesforce/dataloader/dyna/DateOnlyConverter.java new file mode 100644 index 000000000..ad86affee --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dyna/DateOnlyConverter.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.dyna; + +import java.util.*; + +import com.salesforce.dataloader.util.DateOnlyCalendar; +import com.salesforce.dataloader.model.NADateOnlyCalendarValue; +import org.apache.commons.beanutils.Converter; + +public class DateOnlyConverter extends DateTimeConverter implements Converter { + + public DateOnlyConverter(TimeZone tz, boolean useEuroDateFormat) { + super(tz, useEuroDateFormat); + } + + protected Calendar getCalendar(TimeZone tz) { + return DateOnlyCalendar.getInstance(tz); + } + + protected Calendar getNAValueCalendar() { + return NADateOnlyCalendarValue.getInstance(); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/dyna/DateTimeConverter.java b/src/main/java/com/salesforce/dataloader/dyna/DateTimeConverter.java new file mode 100644 index 000000000..5912a7a4b --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dyna/DateTimeConverter.java @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.dyna; + +import java.text.*; +import java.util.*; + +import com.salesforce.dataloader.model.NACalendarValue; +import com.salesforce.dataloader.model.NATextValue; +import org.apache.commons.beanutils.ConversionException; +import org.apache.commons.beanutils.Converter; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +public class DateTimeConverter implements Converter { + + static final TimeZone GMT_TZ = TimeZone.getTimeZone("GMT"); + static final List supportedEuropeanPatterns = getSupportedPatterns(true); + static final List supportedRegularPatterns = getSupportedPatterns(false); + + static Logger logger = DLLogManager.getLogger(DateTimeConverter.class); + /** + * Should we return the default value on conversion errors? + */ + final boolean useEuroDates; + final TimeZone timeZone; + + public DateTimeConverter(TimeZone tz, boolean useEuroDateFormat) { + this.timeZone = tz; + this.useEuroDates = useEuroDateFormat; + } + + private Calendar parseDate(String dateString, DateFormat fmt) { + final ParsePosition pos = new ParsePosition(0); + fmt.setLenient(false); + final Date date = fmt.parse(dateString, pos); + // we only want to use the date if parsing succeeded and used the entire string + if (date != null && pos.getIndex() == dateString.length()) { + Calendar cal = getCalendar(fmt.getTimeZone()); + cal.setTimeInMillis(date.getTime()); + return cal; + } + return null; + } + + @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Object convert(Class type, Object value) { + if (value == null) { + return null; + } + + if(value instanceof NATextValue) { + return getNAValueCalendar(); + } + + if (value instanceof Calendar) { return value; } + + Calendar cal = getCalendar(this.timeZone); + if (value instanceof Date) { + cal.setTimeInMillis(((Date)value).getTime()); + return cal; + } + + String dateString = value.toString().trim(); + int len = dateString.length(); + + if (len == 0) return null; + + TimeZone timeZoneForValue = this.timeZone; + if ("z".equalsIgnoreCase(dateString.substring(len - 1))) { + dateString = dateString.substring(0, len - 1); + timeZoneForValue = GMT_TZ; + } + + for (String pattern : useEuroDates ? supportedEuropeanPatterns : supportedRegularPatterns) { + final DateFormat df = new SimpleDateFormat(pattern); + df.setTimeZone(timeZoneForValue); + cal = parseDate(dateString, df); + if (cal != null) return cal; + } + + DateFormat df = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT); + df.setTimeZone(this.timeZone); + cal = parseDate(dateString, df); + if (cal != null) return cal; + + df = DateFormat.getDateInstance(DateFormat.SHORT); + df.setTimeZone(this.timeZone); + cal = parseDate(dateString, df); + if (cal != null) return cal; + + throw new ConversionException("Failed to parse date: " + value); + } + + // NOTE: Always use this method to get Calendar instance + protected Calendar getCalendar(TimeZone timezone) { + return Calendar.getInstance(timezone); + } + + protected Calendar getNAValueCalendar() { + return NACalendarValue.getInstance(); + } + + /* + * Helper function to produce all the patterns that DL supports. + * These patterns are a subset of patterns supported by Java text.SimpleDateFormat + * https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html + */ + private static List getSupportedPatterns(boolean europeanDates) { + + List basePatterns = new ArrayList(); + + // Extended patterns means using the - delimiter in the date + + List extendedPatterns = new ArrayList(); + extendedPatterns.add("yyyy-MM-dd'T'HH:mm:ss.SSS"); + extendedPatterns.add("yyyy-MM-dd'T'HH:mm:ss"); + extendedPatterns.add("yyyy-MM-dd'T'HH:mm"); + extendedPatterns.add("yyyy-MM-dd'T'HH"); + extendedPatterns.add("yyyy-MM-dd'T'"); //? + extendedPatterns.add("yyyy-MM-dd"); + + //As per ISO 8601 5.2.1.1, when only the days are omitted, a - is necessary between year and month + List extendedPatternsDateOnly = new ArrayList(); + extendedPatternsDateOnly.add("yyyy-MM"); + extendedPatternsDateOnly.add("yyyyMMdd"); + extendedPatternsDateOnly.add("yyyy"); + + // Using a space instead of 'T' to separate date and time + List extendedPatternsWithoutT = new ArrayList(); + extendedPatternsWithoutT.add("yyyy-MM-dd HH:mm:ss.SSS"); + extendedPatternsWithoutT.add("yyyy-MM-dd HH:mm:ss"); + extendedPatternsWithoutT.add("yyyy-MM-dd HH:mm"); + extendedPatternsWithoutT.add("yyyy-MM-dd HH"); + + // Not using anything to deliminate the date elements from each + // other. Matched through known lengths of components. + List basicPatterns = new ArrayList(); + basicPatterns.add("yyyyMMdd'T'HH:mm:ss.SSS"); + basicPatterns.add("yyyyMMdd'T'HH:mm:ss"); + basicPatterns.add("yyyyMMdd'T'HH:mm"); + basicPatterns.add("yyyyMMdd'T'HH"); + basicPatterns.add("yyyyMMdd'T'"); //? + + // Using a space instead of 'T' to separate date and time + List basicPatternsWithoutT = new ArrayList(); + basicPatternsWithoutT.add("yyyyMMdd HH:mm:ss.SSS"); + basicPatternsWithoutT.add("yyyyMMdd HH:mm:ss"); + basicPatternsWithoutT.add("yyyyMMdd HH:mm"); + basicPatternsWithoutT.add("yyyyMMdd HH"); + + //as per the iso 8601 spec + List fullBasicFormats = new ArrayList(); + fullBasicFormats.add("yyyyMMdd'T'HHmmss"); + fullBasicFormats.add("yyyyMMdd'T'HHmm"); + fullBasicFormats.add("yyyyMMdd'T'HH"); + + + List fullBasicFormatsWithoutT = new ArrayList(); + fullBasicFormatsWithoutT.add("yyyyMMdd HHmmss"); + fullBasicFormatsWithoutT.add("yyyyMMdd HHmm"); + fullBasicFormatsWithoutT.add("yyyyMMdd HH"); + + + String baseDate = europeanDates ? "dd/MM/yyyy" : "MM/dd/yyyy"; + + // Using a space instead of 'T' to separate date and time + List slashPatternsWithoutT = new ArrayList(); + slashPatternsWithoutT.add(baseDate +" HH:mm:ss.SSS"); + slashPatternsWithoutT.add(baseDate +" HH:mm:ss"); + slashPatternsWithoutT.add(baseDate +" HH:mm"); + slashPatternsWithoutT.add(baseDate +" HH"); + slashPatternsWithoutT.add(baseDate +" HHZ"); + slashPatternsWithoutT.add(baseDate); + + List slashPatternsWithT = new ArrayList(); + slashPatternsWithT.add(baseDate + "'T'HH:mm:ss.SSS"); + slashPatternsWithT.add(baseDate + "'T'HH:mm:ss"); + slashPatternsWithT.add(baseDate + "'T'HH:mm"); + slashPatternsWithT.add(baseDate + "'T'HH"); + + //order is important here because if it matches against the wrong format first, it will + //misinterpret the time + + basePatterns.addAll(fullBasicFormatsWithoutT); + basePatterns.addAll(fullBasicFormats); + basePatterns.addAll(basicPatterns); + basePatterns.addAll(basicPatternsWithoutT); + basePatterns.addAll(extendedPatternsDateOnly); + basePatterns.addAll(extendedPatterns); + basePatterns.addAll(extendedPatternsWithoutT); + basePatterns.addAll(slashPatternsWithoutT); + basePatterns.addAll(slashPatternsWithT); + + List timeZones = new ArrayList<>(); + // uppercase Z => RFC822 TimeZone + basePatterns.forEach(p -> timeZones.add(p + "Z")); + basePatterns.forEach(p -> timeZones.add(p + " Z")); + + // uppercase X => ISO8601 TimeZone + basePatterns.forEach(p -> timeZones.add(p + "XXX")); + basePatterns.forEach(p -> timeZones.add(p + " XXX")); + + basePatterns.forEach(p -> timeZones.add(p + "'Z'Z")); + basePatterns.forEach(p -> timeZones.add(p + "'z'Z")); + basePatterns.forEach(p -> timeZones.add(p + "z")); + + basePatterns.addAll(timeZones); + + return basePatterns; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/dyna/DoubleConverter.java b/src/main/java/com/salesforce/dataloader/dyna/DoubleConverter.java index 7ca20d88c..4687eac26 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/DoubleConverter.java +++ b/src/main/java/com/salesforce/dataloader/dyna/DoubleConverter.java @@ -40,31 +40,8 @@ public final class DoubleConverter implements Converter { // ----------------------------------------------------------- Constructors public DoubleConverter() { - - this.defaultValue = null; - this.useDefault = false; - } - public DoubleConverter(Object defaultValue) { - - this.defaultValue = defaultValue; - this.useDefault = true; - - } - - // ----------------------------------------------------- Instance Variables - - /** - * The default value specified to our Constructor, if any. - */ - private Object defaultValue = null; - - /** - * Should we return the default value on conversion errors? - */ - private boolean useDefault = true; - // --------------------------------------------------------- Public Methods /** @@ -78,22 +55,19 @@ public DoubleConverter(Object defaultValue) { * if conversion cannot be performed successfully */ @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object convert(Class type, Object value) { if (value == null || String.valueOf(value).length() == 0) { return null; } if (value instanceof Double) { return (value); - } else if (value instanceof Number) { return new Double(((Number)value).doubleValue()); } + } else if (value instanceof Number) { return ((Number) value).doubleValue(); } try { - return (new Double(value.toString())); + return (Double.valueOf(value.toString())); } catch (Exception e) { - if (useDefault) { - return (defaultValue); - } else { - throw new ConversionException(e); - } + throw new ConversionException(e); } } diff --git a/src/main/java/com/salesforce/dataloader/dyna/FileByteArrayConverter.java b/src/main/java/com/salesforce/dataloader/dyna/FileByteArrayConverter.java index 51a56c22d..0a62e3d51 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/FileByteArrayConverter.java +++ b/src/main/java/com/salesforce/dataloader/dyna/FileByteArrayConverter.java @@ -27,10 +27,18 @@ package com.salesforce.dataloader.dyna; import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; import org.apache.commons.beanutils.ConversionException; import org.apache.commons.beanutils.Converter; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.ws.util.FileUtil; /** @@ -40,35 +48,14 @@ */ public final class FileByteArrayConverter implements Converter { + private static Logger logger; // ----------------------------------------------------------- Constructors public FileByteArrayConverter() { - - this.defaultValue = null; - this.useDefault = false; - - } - - public FileByteArrayConverter(Object defaultValue) { - - this.defaultValue = defaultValue; - this.useDefault = true; - + logger = DLLogManager.getLogger(this.getClass()); } - // ----------------------------------------------------- Instance Variables - - /** - * The default value specified to our Constructor, if any. - */ - private Object defaultValue = null; - - /** - * Should we return the default value on conversion errors? - */ - private boolean useDefault = true; - // --------------------------------------------------------- Public Methods /** @@ -82,21 +69,55 @@ public FileByteArrayConverter(Object defaultValue) { * if conversion cannot be performed successfully */ @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object convert(Class type, Object value) { if (value == null || String.valueOf(value).length() == 0) { return null; } - + final String absolutePath = new File(String.valueOf(value.toString())).getAbsolutePath(); + final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); try { - final ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); // just in case the file is not found we want to display the absolute file name to the user - final String absolutePath = new File(String.valueOf(value.toString())).getAbsolutePath(); + File file = new File(absolutePath); + if (!file.canRead()) { + logger.debug("Attempting to enable readable flag on file " + absolutePath); + file.setReadable(true); + } FileUtil.copy(new FileInputStream(absolutePath), byteStream); - return byteStream.toByteArray(); - } catch (Exception e) { - if (useDefault) { - return (defaultValue); + Path pathToValueFile = Path.of(absolutePath); + String mimeType = Files.probeContentType(pathToValueFile); + AppConfig appConfig = AppConfig.getCurrentConfig(); + if (mimeType != null + && mimeType.equalsIgnoreCase("text/plain") + && appConfig != null + && appConfig.getBoolean(AppConfig.PROP_LOAD_PRESERVE_WHITESPACE_IN_RICH_TEXT) + && AppUtil.isContentSObject(appConfig.getString(AppConfig.PROP_ENTITY))) { + // Preserve the formatting only if the content is of type plain text + // AND the flag to preserve whitespace characters in RichText fields is enabled + // AND the content is for ContentNote sobject. + // See https://help.salesforce.com/s/articleView?id=000387816&type=1 for how + // data loader processes ContentNote. + String content = byteStream.toString(); + String formattedContent = DAOLoadVisitor.convertToHTMLFormatting(content, AppConfig.DEFAULT_RICHTEXT_REGEX); + return formattedContent.getBytes(); } else { - throw new ConversionException(e); + return byteStream.toByteArray(); + } + } catch (Exception e) { + if (e instanceof java.io.FileNotFoundException) { + if (AppUtil.getOSType() == AppUtil.OSType.MACOSX + && (absolutePath.contains("/Desktop/") || absolutePath.contains("/Downloads/"))) { + logger.error(Messages.getMessage(this.getClass(), "insufficientAccessToContentOnMacMsg1", absolutePath)); + logger.error(Messages.getMessage(this.getClass(), "insufficientAccessToContentOnMacMsg2")); + } else { + logger.error(Messages.getMessage(this.getClass(), "insufficientAccessToContentGenericMsg", absolutePath)); + } + } + throw new ConversionException(e); + } finally { + try { + byteStream.close(); + } catch (Exception ex) { + // do nothing } } } diff --git a/src/main/java/com/salesforce/dataloader/dyna/IntegerConverter.java b/src/main/java/com/salesforce/dataloader/dyna/IntegerConverter.java index 4af5dbd5c..a22a66a4d 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/IntegerConverter.java +++ b/src/main/java/com/salesforce/dataloader/dyna/IntegerConverter.java @@ -26,6 +26,11 @@ package com.salesforce.dataloader.dyna; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.ParseException; +import java.util.Locale; + import org.apache.commons.beanutils.ConversionException; import org.apache.commons.beanutils.Converter; @@ -39,31 +44,8 @@ public final class IntegerConverter implements Converter { public IntegerConverter() { - - this.defaultValue = null; - this.useDefault = false; - - } - - public IntegerConverter(Object defaultValue) { - - this.defaultValue = defaultValue; - this.useDefault = true; - } - // ----------------------------------------------------- Instance Variables - - /** - * The default value specified to our Constructor, if any. - */ - private Object defaultValue = null; - - /** - * Should we return the default value on conversion errors? - */ - private boolean useDefault = true; - // --------------------------------------------------------- Public Methods /** @@ -77,6 +59,7 @@ public IntegerConverter(Object defaultValue) { * if conversion cannot be performed successfully */ @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object convert(Class type, Object value) { if (value == null || String.valueOf(value).length() == 0) { return null; @@ -85,17 +68,16 @@ public Object convert(Class type, Object value) { if (value instanceof Integer) { return (value); } else if (value instanceof Number) { - return new Integer(((Number)value).intValue()); + return Integer.valueOf(((Number)value).intValue()); } - + try { - return (new Integer(value.toString())); - } catch (Exception e) { - if (useDefault) { - return (defaultValue); - } else { - throw new ConversionException(e); - } + NumberFormat numFormat = DecimalFormat.getIntegerInstance(Locale.getDefault()); + numFormat.setParseIntegerOnly(true); + Number number = numFormat.parse(value.toString()); + return Integer.valueOf(number.intValue()); + } catch (ParseException e) { + throw new ConversionException(e); } } diff --git a/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java b/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java deleted file mode 100644 index 7f37e44bc..000000000 --- a/src/main/java/com/salesforce/dataloader/dyna/ObjectField.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.dyna; - - -/** - * Container for an object field of format - * objectName:fieldName - * - * @author Alex Warshavsky - * @since 8.0 - */ -public class ObjectField { - private String objectName; - private String fieldName; - public static final String VALUE_SEPARATOR_CHAR = ":"; //$NON-NLS-1$ - - /** - * @param objectName - * @param fieldName - */ - public ObjectField(String objectName, String fieldName) { - this.objectName = objectName; - this.fieldName = fieldName; - } - - /** - * @param objectField - */ - public ObjectField(String objectField) { - String[] refFieldNameInfo = objectField.split(ObjectField.VALUE_SEPARATOR_CHAR); - objectName = refFieldNameInfo[0]; - fieldName = refFieldNameInfo[1]; - } - - /** - * @param objectFieldArray - */ - public ObjectField(String[] objectFieldArray) { - objectName = objectFieldArray[0]; - fieldName = objectFieldArray[1]; - } - - public String getFieldName() { - return fieldName; - } - - public String getObjectName() { - return objectName; - } - - /** - * @param objectName - * @param fieldName - * @return String formatted as objectName:fieldName - */ - static public String formatAsString(String objectName, String fieldName) { - return objectName + ObjectField.VALUE_SEPARATOR_CHAR + fieldName; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return formatAsString(objectName, fieldName); - } -} diff --git a/src/main/java/com/salesforce/dataloader/dyna/ParentIdLookupFieldFormatter.java b/src/main/java/com/salesforce/dataloader/dyna/ParentIdLookupFieldFormatter.java new file mode 100644 index 000000000..b261d2d78 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dyna/ParentIdLookupFieldFormatter.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.dyna; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.exception.RelationshipFormatException; + + +public class ParentIdLookupFieldFormatter { + private String parentFieldName = null; + private ParentSObjectFormatter parentSObjectFormatter = null; + private static final Logger logger = DLLogManager.getLogger(ParentIdLookupFieldFormatter.class); + + private static final String OLD_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR = ":"; //$NON-NLS-1$ + // old format - : + // Example - "Owner:username" where Account.Owner field is a lookup + // field to User object, username is an idLookup field in User + // + // new format to support polymorphic lookup relationship - + // :. + // Example - "Account:Owner.username" + public static final String NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR = "-"; + + // formattedFieldName param can be in one of the following formats: + // format 1: alphanumeric string without any ':' or '-' in it. Represents name of child's non-polymorphic relationship field + // format 1 => it is name of a non-polymorphic relationship field in child object. + // + // format 2: alphanumeric string with a ':' in it + // format 2 has 2 interpretations: + // 2a - (legacy format): + // : + // 2b - (new format): + // : + // + // format 3: alphanumeric string with a single ':' and a single '-' in it + // format 3 => it is name of a field in child object with reference to an idlookup field in parent object + // + // Given 2 interpretations of format 2, an additional parameter, 'isFieldName', is required. + // If 'hasParentIdLookupFieldName' == true, the code processes fieldName parameter according + // to the 2nd interpretation for format 2. It processes fieldName parameter according to 1st interpretation otherwise. + + public ParentIdLookupFieldFormatter(String formattedFieldName) throws RelationshipFormatException { + if (formattedFieldName == null || formattedFieldName.isBlank()) { + throw new RelationshipFormatException("parent idLookup field name not specified"); + } + String[] fieldNameParts = formattedFieldName.split(ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR); + boolean hasParentIdLookupFieldName = false; + if (fieldNameParts.length == 2) { + // parent name not specified + parentFieldName = fieldNameParts[1]; + hasParentIdLookupFieldName = true; // '.' char shows up only in format 3 + formattedFieldName = fieldNameParts[0]; + } + fieldNameParts = formattedFieldName.split(ParentSObjectFormatter.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR); + String relationshipName = null; + String parentObjectName = null; + if (hasParentIdLookupFieldName) { // format 3 + if (fieldNameParts.length == 2) { + relationshipName = fieldNameParts[0]; + parentObjectName = fieldNameParts[1]; + } else { // Should not happen - no ':' char in name, may have '-' char + if (parentFieldName == null) { // no ':' and no '.' in name + String errorStr = "field name " + formattedFieldName + " does not have ':' or '-' char"; + logger.error(errorStr); + throw new RelationshipFormatException(errorStr); + } else { + // '-' char in name but no ':' + String errorStr = "field name " + formattedFieldName + " has '-' but does not have ':' char"; + logger.error(errorStr); + throw new RelationshipFormatException(errorStr); + } + } + } else { // format 1 or format 2 + if (fieldNameParts.length == 2) { // format 2 + relationshipName = fieldNameParts[0]; + parentFieldName = fieldNameParts[1]; + } else { // format 1 + relationshipName = formattedFieldName; + } + } + parentSObjectFormatter = new ParentSObjectFormatter(parentObjectName, relationshipName); + } + + public ParentIdLookupFieldFormatter(String parentObjectName, String relationshipName, String parentIdLookupFieldName) throws RelationshipFormatException { + parentSObjectFormatter = new ParentSObjectFormatter(parentObjectName, relationshipName); + this.parentFieldName = parentIdLookupFieldName; + } + + public String getParentFieldName() { + return parentFieldName; + } + + public ParentSObjectFormatter getParent() { + return parentSObjectFormatter; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + String separator = ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR; + if (parentSObjectFormatter.getParentObjectName() == null) { + separator = ParentIdLookupFieldFormatter.OLD_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR; + } + return parentSObjectFormatter.toString() + separator + parentFieldName; + } +} diff --git a/src/main/java/com/salesforce/dataloader/dyna/ParentSObjectFormatter.java b/src/main/java/com/salesforce/dataloader/dyna/ParentSObjectFormatter.java new file mode 100644 index 000000000..c9e4da42f --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/dyna/ParentSObjectFormatter.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.dyna; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.exception.RelationshipFormatException; + +/** + * Container for an object field of format + * objectName:fieldName + * + * @author Alex Warshavsky + * @since 8.0 + */ +public class ParentSObjectFormatter { + private String relationshipName; + private String parentObjectName = null; + private static final Logger logger = DLLogManager.getLogger(ParentSObjectFormatter.class); + + public static final String NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR = ":"; + + public ParentSObjectFormatter(String parentObjectName, String relationshipName) throws RelationshipFormatException{ + initialize(parentObjectName, relationshipName); + } + + // parentAndRelationshipName param can be in one of the following formats: + // format 1: alphanumeric string without any ':' or '-' in it. Represents name of child's non-polymorphic relationship field + // format 1 => it is name of a non-polymorphic relationship field in child object. + // + // format 2: alphanumeric string with a ':' in it + // format 2 has 1 interpretations: + // interpretation 1: : + // - this is the new format for keys of the hashmap referenceEntitiesDescribeMap + + public ParentSObjectFormatter(String formattedName) throws RelationshipFormatException { + String relationshipName = null; + String parentObjectName = null; + if (formattedName == null) { + throw new RelationshipFormatException("relationship parent name not specified"); + } + String[] fieldNameParts = formattedName.split(ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR); + if (fieldNameParts.length == 2) { // discard the part containing parent's idLookup field name + formattedName = fieldNameParts[0]; + } + fieldNameParts = formattedName.split(ParentSObjectFormatter.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR); + if (fieldNameParts.length == 2) { // format 2, interpretation 1 + relationshipName = fieldNameParts[0]; + parentObjectName = fieldNameParts[1]; + } else { // format 1 + relationshipName = formattedName; + } + initialize(parentObjectName, relationshipName); + } + + public void setParentObjectName(String name) { + this.parentObjectName = name; + } + + private void initialize(String parentObjectName, String relationshipName) throws RelationshipFormatException{ + if ((relationshipName == null || relationshipName.isBlank())) { + throw new RelationshipFormatException("Relationship name not specified"); + } + this.parentObjectName = parentObjectName; + this.relationshipName = relationshipName; + } + + public String getRelationshipName() { + return relationshipName; + } + + public String getParentObjectName() { + return parentObjectName; + } + + public String toString() { + if (parentObjectName == null) { + return relationshipName; + } + return relationshipName + + ParentSObjectFormatter.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR + + parentObjectName; + } + + public boolean matches(String nameToCompareWith) { + if (relationshipName == null) { + return false; + } + if (parentObjectName == null) { + return nameToCompareWith.toLowerCase().startsWith(relationshipName.toLowerCase()); + } else { + return nameToCompareWith.toLowerCase().equalsIgnoreCase(relationshipName + NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR + parentObjectName); + } + } +} diff --git a/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java b/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java index 79353829a..f4b5882e6 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java +++ b/src/main/java/com/salesforce/dataloader/dyna/SObjectReference.java @@ -26,10 +26,14 @@ package com.salesforce.dataloader.dyna; import org.apache.commons.beanutils.ConvertUtils; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; import com.salesforce.dataloader.client.DescribeRefObject; +import com.salesforce.dataloader.client.SObject4JSON; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.exception.RelationshipFormatException; import com.sforce.soap.partner.Field; import com.sforce.soap.partner.sobject.SObject; @@ -42,6 +46,7 @@ public class SObjectReference { private final Object referenceExtIdValue; + private static final Logger logger = DLLogManager.getLogger(SObjectReference.class); /** * @param refValue @@ -57,25 +62,34 @@ public SObjectReference(Object refValue) { * @param refFieldName * @throws ParameterLoadException */ - public void addReferenceToSObject(Controller controller, SObject sObj, String refFieldName) throws ParameterLoadException { + public void addReferenceToSObject(Controller controller, SObject sObj, SObject4JSON restSObj, String refFieldName) throws ParameterLoadException { // break the name into relationship and field name components - ObjectField refField = new ObjectField(refFieldName); - String relationshipName = refField.getObjectName(); - String fieldName = refField.getFieldName(); + ParentIdLookupFieldFormatter refField; + try { + refField = new ParentIdLookupFieldFormatter(refFieldName); + } catch (RelationshipFormatException e) { + logger.error(e.getMessage()); + return; + } + String relationshipName = refField.getParent().getRelationshipName(); + String parentFieldName = refField.getParentFieldName(); - // get object info for the given reference (foreign key) relationship - DescribeRefObject entityRefInfo = controller.getReferenceDescribes().get(relationshipName); + DescribeRefObject entityRefInfo = controller.getReferenceDescribes().getParentSObject(refField.getParent().toString()); // build the reference SObject SObject sObjRef = new SObject(); // set entity type, has to be set before all others - sObjRef.setType(entityRefInfo.getObjectName()); - // set external id, do type conversion as well - Class typeClass = SforceDynaBean.getConverterClass(entityRefInfo.getFieldInfoMap().get(fieldName)); + sObjRef.setType(entityRefInfo.getParentObjectName()); + // set idLookup, do type conversion as well + Class typeClass = SforceDynaBean.getConverterClass(entityRefInfo.getParentObjectFieldMap().get(parentFieldName)); Object extIdValue = ConvertUtils.convert(this.referenceExtIdValue.toString(), typeClass); - sObjRef.setField(fieldName, extIdValue); + sObjRef.setField(parentFieldName, extIdValue); // Add the sObject reference as a child elemetn, name set to relationshipName - sObj.addField(relationshipName, sObjRef); + if (restSObj == null) { + sObj.addField(relationshipName, sObjRef); + } else { + restSObj.setField(relationshipName, sObjRef); + } } @Override @@ -100,8 +114,14 @@ public Object getReferenceExtIdValue() { } public static String getRelationshipField(Controller controller, String refFieldName) { - final String relName = new ObjectField(refFieldName).getObjectName(); - controller.getReferenceDescribes().get(relName).getFieldInfoMap(); + String relName; + try { + relName = new ParentIdLookupFieldFormatter(refFieldName).getParent().getRelationshipName(); + } catch (RelationshipFormatException e) { + logger.error(e.getMessage()); + return null; + } + controller.getReferenceDescribes().getParentSObject(relName).getParentObjectFieldMap(); for (Field f : controller.getFieldTypes().getFields()) { if (f != null) { if (relName.equals(f.getRelationshipName())) { return f.getName(); } diff --git a/src/main/java/com/salesforce/dataloader/dyna/SObjectReferenceConverter.java b/src/main/java/com/salesforce/dataloader/dyna/SObjectReferenceConverter.java index 787e5d86c..59e9716f5 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/SObjectReferenceConverter.java +++ b/src/main/java/com/salesforce/dataloader/dyna/SObjectReferenceConverter.java @@ -40,35 +40,8 @@ public class SObjectReferenceConverter implements Converter { // ----------------------------------------------------------- Constructors public SObjectReferenceConverter() { - - this.defaultValue = null; - this.useDefault = false; - } - public SObjectReferenceConverter(Object defaultValue) { - - this.defaultValue = defaultValue; - this.useDefault = true; - - } - - - // ----------------------------------------------------- Instance Variables - - - /** - * The default value specified to our Constructor, if any. - */ - private Object defaultValue = null; - - - /** - * Should we return the default value on conversion errors? - */ - private boolean useDefault = true; - - // --------------------------------------------------------- Public Methods @@ -83,17 +56,14 @@ public SObjectReferenceConverter(Object defaultValue) { * successfully */ @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object convert(Class type, Object value) { try { Object refValue = value; SObjectReference sObjectRefValue = new SObjectReference(refValue); return sObjectRefValue; } catch (ClassCastException e) { - if (useDefault) { - return defaultValue; - } else { - throw new ConversionException(e); - } + throw new ConversionException(e); } } diff --git a/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java b/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java index 4551898d0..124e3d3e8 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java +++ b/src/main/java/com/salesforce/dataloader/dyna/SforceDynaBean.java @@ -28,17 +28,24 @@ import java.lang.reflect.InvocationTargetException; import java.util.*; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.RowInterface; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.DateOnlyCalendar; + import org.apache.commons.beanutils.*; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; import com.salesforce.dataloader.client.DescribeRefObject; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.client.SObject4JSON; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.LoadException; import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.exception.RelationshipFormatException; import com.sforce.soap.partner.*; import com.sforce.soap.partner.sobject.SObject; @@ -51,7 +58,7 @@ public class SforceDynaBean { //logger - public static Logger logger = Logger.getLogger(DAOLoadVisitor.class); + public static Logger logger = DLLogManager.getLogger(DAOLoadVisitor.class); /** * @@ -75,22 +82,41 @@ static public DynaProperty[] createDynaProps(DescribeSObjectResult describer, Co for (Field field : fields) { String fieldName = field.getName(); //see which class type equals the field type - Class classType = getConverterClass(field); + Class classType = getConverterClass(field); dynaProps.add(new DynaProperty(fieldName, classType)); // if field is a reference to another object, remember the reference // NOTE: currently only fields with one reference are supported on the server FieldType fieldType = field.getType(); String relationshipName = field.getRelationshipName(); - if (fieldType == FieldType.reference && field.getReferenceTo().length == 1 && + if (fieldType == FieldType.reference && relationshipName != null && relationshipName.length() > 0) { - - DescribeRefObject refInfo = controller.getReferenceDescribes().get(relationshipName); - if(refInfo != null) { - for(String refFieldName : refInfo.getFieldInfoMap().keySet()) { - // property name contains information for mapping - dynaProps.add(new DynaProperty(ObjectField.formatAsString(relationshipName, refFieldName), - SObjectReference.class)); + for (String parentName : field.getReferenceTo()) { + ParentSObjectFormatter parentHandleForRelationship; + try { + parentHandleForRelationship = new ParentSObjectFormatter(parentName, relationshipName); + } catch (RelationshipFormatException e) { + logger.error(e.getMessage()); + continue; + } + DescribeRefObject parent = controller.getReferenceDescribes().getParentSObject(parentHandleForRelationship.toString()); + if(parent != null) { + for(String refFieldName : parent.getParentObjectFieldMap().keySet()) { + // property name contains information for mapping + // add old format to dyna props + try { + dynaProps.add(new DynaProperty( + new ParentIdLookupFieldFormatter(null, relationshipName, refFieldName).toString(), + SObjectReference.class)); + dynaProps.add(new DynaProperty( + new ParentIdLookupFieldFormatter(parent.getParentObjectName(), relationshipName, refFieldName).toString(), + SObjectReference.class)); + } catch (RelationshipFormatException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + } + // add new format to dyna props + } } } } @@ -130,7 +156,7 @@ public static Class getTypeClass(Field field) { classType = Boolean.class; break; case date: - classType = Calendar.class; + classType = Date.class; break; case base64Binary: classType = byte[].class; @@ -173,7 +199,7 @@ public static Class getConverterClass(Field field) { classType = Boolean.class; break; case date: - classType = Calendar.class; + classType = DateOnlyCalendar.class; break; case base64Binary: classType = byte[].class; @@ -200,15 +226,25 @@ static public BasicDynaClass getDynaBeanInstance(DynaProperty[] dynaProps) { * @throws ConversionException * @throws LoadException */ - static public DynaBean convertToDynaBean(BasicDynaClass dynaClass, Row sforceDataRow) + static public DynaBean convertToDynaBean(BasicDynaClass dynaClass, RowInterface sforceDataRow) throws ConversionException, LoadException { //now convert the data types, through our strongly typed bean DynaBean sforceObj = null; try { sforceObj = dynaClass.newInstance(); - //This does an automatic conversion of types. BeanUtils.copyProperties(sforceObj, sforceDataRow); + for (String sforceField : sforceDataRow.getColumnNames()) { + Object val = sforceDataRow.get(sforceField); + if (val != null + && val instanceof String + && !((String)val).isBlank() + && sforceObj.get(sforceField) == null) { + String errStr = "unable to convert a non-null " + sforceField + "value " + (String)val + " to a field on entity " + AppConfig.getCurrentConfig().getString(AppConfig.PROP_ENTITY); + logger.error(errStr); //$NON-NLS-1$ + throw new LoadException(errStr); + } + } return sforceObj; } catch (IllegalAccessException e1) { @@ -229,23 +265,40 @@ static public DynaBean convertToDynaBean(BasicDynaClass dynaClass, Row sforceDat * @param sObj * @param dynaBean */ - static public void insertNullArray(Controller controller, SObject sObj, DynaBean dynaBean) { - final List fieldsToNull = new LinkedList(); + static public void insertNullArrayForSOAP(Controller controller, SObject sObj, DynaBean dynaBean) { + final List fieldsToNull = getFieldsToNull(controller, dynaBean); + if (fieldsToNull.size() > 0) sObj.setFieldsToNull(fieldsToNull.toArray(new String[fieldsToNull.size()])); + } + + static public void insertNullArrayForREST(Controller controller, Map sObj, DynaBean dynaBean) { + final List fieldsToNull = getFieldsToNull(controller, dynaBean); + for (String field : fieldsToNull) { + sObj.put(field, null); + } + } + + static public List getFieldsToNull(Controller controller, DynaBean dynaBean) { + final List fieldsToNull = new ArrayList(); for (String sfdcField : controller.getMapper().getDestColumns()) { handleNull(sfdcField, dynaBean, fieldsToNull, controller); } for (Map.Entry constantEntry : controller.getMapper().getConstantsMap().entrySet()) { handleNull(constantEntry.getKey(), dynaBean, fieldsToNull, controller); } - if (fieldsToNull.size() > 0) sObj.setFieldsToNull(fieldsToNull.toArray(new String[fieldsToNull.size()])); + return fieldsToNull; } - - private static void handleNull(final String fieldName, final DynaBean dynaBean, final List fieldsToNull, + + private static void handleNull(final String fieldNameList, final DynaBean dynaBean, final List fieldsToNull, final Controller controller) { - final Object o = dynaBean.get(fieldName); - if (o != null && o instanceof SObjectReference && ((SObjectReference)o).isNull()) - fieldsToNull.add(SObjectReference.getRelationshipField(controller, fieldName)); - else if (o == null || String.valueOf(o).length() == 0) fieldsToNull.add(fieldName); + // fieldNameList can be a list of comma separated fields + String[]fieldNameArray = fieldNameList.split(","); + + for (String fieldName : fieldNameArray) { + final Object o = dynaBean.get(fieldName.strip()); + if (o != null && o instanceof SObjectReference && ((SObjectReference)o).isNull()) + fieldsToNull.add(SObjectReference.getRelationshipField(controller, fieldName)); + else if (o == null || String.valueOf(o).length() == 0) fieldsToNull.add(fieldName); + } } /** @@ -262,17 +315,35 @@ static public SObject[] getSObjectArray(Controller controller, List dy for (int j = 0; j < sObjects.length; j++) { DynaBean dynaBean = dynaBeans.get(j); - SObject sObj = getSObject(controller, entityName, dynaBean); + SObject sObj = getSOAPSObject(controller, entityName, dynaBean); // if we are inserting nulls, build the null array if (insertNulls) { - insertNullArray(controller, sObj, dynaBean); + insertNullArrayForSOAP(controller, sObj, dynaBean); } sObjects[j] = sObj; } return sObjects; } + + static public List> getRESTSObjectArray(Controller controller, List dynaBeans, String entityName, boolean insertNulls) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParameterLoadException { + List> restSObjects = new ArrayList >(); + + for (int j = 0; j < dynaBeans.size(); j++) { + DynaBean dynaBean = dynaBeans.get(j); + + Map sObj = getCompositeRESTSObject(controller, entityName, dynaBean); + + // if we are inserting nulls, build the null array + if (insertNulls) { + insertNullArrayForREST(controller, sObj, dynaBean); + } + + restSObjects.add(sObj); + } + return restSObjects; + } /** * @param entityName @@ -283,37 +354,74 @@ static public SObject[] getSObjectArray(Controller controller, List dy * @throws NoSuchMethodException * @throws ParameterLoadException */ - @SuppressWarnings("unchecked") - public static SObject getSObject(Controller controller, String entityName, DynaBean dynaBean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParameterLoadException { + public static SObject getSOAPSObject(Controller controller, String entityName, DynaBean dynaBean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParameterLoadException { SObject sObj = new SObject(); sObj.setType(entityName); - Map fieldMap = BeanUtils.describe(dynaBean); + Map fieldMap = BeanUtils.describe(dynaBean); for (String fName : fieldMap.keySet()) { if (fieldMap.get(fName) != null) { // see if any entity foreign key references are embedded here Object value = dynaBean.get(fName); - if (value != null && value instanceof SObjectReference) { + if (value instanceof SObjectReference) { SObjectReference sObjRef = (SObjectReference)value; - if (!sObjRef.isNull()) sObjRef.addReferenceToSObject(controller, sObj, fName); + if (!sObjRef.isNull()) sObjRef.addReferenceToSObject(controller, sObj, null, fName); } else { - sObj.setField(fName, dynaBean.get(fName)); + sObj.setField(fName, value); } } } return sObj; } + + public static Map getCompositeRESTSObject(Controller controller, String entityName, DynaBean dynaBean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException, ParameterLoadException { + SObject4JSON restSObj = new SObject4JSON(entityName); + Map fieldMap = BeanUtils.describe(dynaBean); + for (String fName : fieldMap.keySet()) { + if (fieldMap.get(fName) != null) { + // see if any entity foreign key references are embedded here + Object value = dynaBean.get(fName); + if (value instanceof SObjectReference) { + try { + ParentIdLookupFieldFormatter idLookupFieldFormatter = new ParentIdLookupFieldFormatter(fName); + DescribeSObjectResult parentSObjectDescribe = controller.getPartnerClient().describeSObject(idLookupFieldFormatter.getParent().getParentObjectName()); + DynaProperty[] parentDynaProps = createDynaProps(parentSObjectDescribe, controller); + BasicDynaClass parentDynaClass = getDynaBeanInstance(parentDynaProps); + ArrayList parentLookupFieldList = new ArrayList(); + parentLookupFieldList.add(idLookupFieldFormatter.getParentFieldName()); + TableHeader header = new TableHeader(parentLookupFieldList); + TableRow parentDataRow = new TableRow(header); + parentDataRow.put(idLookupFieldFormatter.getParentFieldName(), value); + DynaBean parentDynaBean = convertToDynaBean(parentDynaClass, parentDataRow); + Map parentRESTSObject = getCompositeRESTSObject(controller, idLookupFieldFormatter.getParent().getParentObjectName(), parentDynaBean); + SObjectReference sObjRef = (SObjectReference)value; + if (!sObjRef.isNull()) { + restSObj.setField(idLookupFieldFormatter.getParent().getRelationshipName(), + parentRESTSObject); + } + } catch (Exception e) { + e.printStackTrace(); + logger.error("Unable to convert " + fName + " to parent reference"); + } + } else { + restSObj.setField(fName, value); + } + } + } + return restSObj.getRepresentationForCompositeREST(); + } /** * Register dynabean data type converters for common java data types * @param useEuroDates if true, european date format will be used */ - synchronized static public void registerConverters(Config cfg) { - final boolean useEuroDates = cfg.getBoolean(Config.EURO_DATES); + synchronized static public void registerConverters(AppConfig cfg) { + final boolean useEuroDates = cfg.getBoolean(AppConfig.PROP_EURO_DATES); final TimeZone tz = cfg.getTimeZone(); // Register DynaBean type conversions - ConvertUtils.register(new DateConverter(tz, useEuroDates), Calendar.class); + ConvertUtils.register(new DateTimeConverter(tz, useEuroDates), Calendar.class); + ConvertUtils.register(new DateOnlyConverter(tz, useEuroDates), DateOnlyCalendar.class); ConvertUtils.register(new DoubleConverter(), Double.class); - ConvertUtils.register(new IntegerConverter(null), Integer.class); + ConvertUtils.register(new IntegerConverter(), Integer.class); ConvertUtils.register(new BooleanConverter(), Boolean.class); ConvertUtils.register(new StringConverter(), String.class); ConvertUtils.register(new FileByteArrayConverter(), byte[].class); diff --git a/src/main/java/com/salesforce/dataloader/dyna/StringConverter.java b/src/main/java/com/salesforce/dataloader/dyna/StringConverter.java index b869cc20b..b7abfce39 100644 --- a/src/main/java/com/salesforce/dataloader/dyna/StringConverter.java +++ b/src/main/java/com/salesforce/dataloader/dyna/StringConverter.java @@ -50,35 +50,8 @@ public final class StringConverter implements Converter { // ----------------------------------------------------------- Constructors public StringConverter() { - - this.defaultValue = null; - this.useDefault = false; - - } - - public StringConverter(Object defaultValue) { - - this.defaultValue = defaultValue; - this.useDefault = true; - } - - // ----------------------------------------------------- Instance Variables - - - /** - * The default value specified to our Constructor, if any. - */ - private Object defaultValue = null; - - - /** - * Should we return the default value on conversion errors? - */ - private boolean useDefault = true; - - // --------------------------------------------------------- Public Methods @@ -93,6 +66,7 @@ public StringConverter(Object defaultValue) { * successfully */ @Override + @SuppressWarnings({ "rawtypes", "unchecked" }) public Object convert(Class type, Object value) { if (value == null || String.valueOf(value).isEmpty()) { @@ -118,11 +92,7 @@ public Object convert(Class type, Object value) { try { return cleanseString(value.toString()); } catch (ClassCastException e) { - if (useDefault) { - return (defaultValue); - } else { - throw new ConversionException(e); - } + throw new ConversionException(e); } } @@ -156,6 +126,10 @@ private String cleanseString(String value) { default: if (((c >= 0x20) && (c <= 0xD7FF)) || ((c >= 0xE000) && (c <= 0xFFFD))) { buff.append(c); + } else if (Character.isHighSurrogate(c) && (i + 1) < value.length() && Character.isLowSurrogate(value.charAt(i + 1))) { + buff.append(c); + buff.append(value.charAt(i + 1)); + i++; } // For chars outside these ranges (such as control chars), // do nothing; it's not legal XML to print these chars, diff --git a/src/main/java/com/salesforce/dataloader/exception/BatchSizeLimitException.java b/src/main/java/com/salesforce/dataloader/exception/BatchSizeLimitException.java new file mode 100644 index 000000000..d47cc7aac --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/exception/BatchSizeLimitException.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.exception; + +/** + * Describe your class here. + * + * @author Alex Warshavsky + * @since 8.0 + */ +@SuppressWarnings("serial") +public class BatchSizeLimitException extends Exception { + + /** + * + */ + public BatchSizeLimitException() { + super(); + } + + /** + * @param message + */ + public BatchSizeLimitException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public BatchSizeLimitException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param cause + */ + public BatchSizeLimitException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/exception/ExtractExceptionOnServer.java b/src/main/java/com/salesforce/dataloader/exception/ExtractExceptionOnServer.java new file mode 100644 index 000000000..2a2b95092 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/exception/ExtractExceptionOnServer.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.exception; + +/** + * Exception during Extraction + * + * @author Lexi Viripaeff + * @since 6.0 + */ +@SuppressWarnings("serial") +public class ExtractExceptionOnServer extends ExtractException { + + public ExtractExceptionOnServer() { + super(); + } + + public ExtractExceptionOnServer(String message) { + super(message); + } + + public ExtractExceptionOnServer(Throwable cause) { + super(cause); + } + + public ExtractExceptionOnServer(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/exception/HttpClientTransportException.java b/src/main/java/com/salesforce/dataloader/exception/HttpClientTransportException.java new file mode 100644 index 000000000..8edae9124 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/exception/HttpClientTransportException.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.exception; + +import java.io.InputStream; +import java.net.HttpURLConnection; + +/** + * + * @since 60.0 + */ +@SuppressWarnings("serial") +public class HttpClientTransportException extends OperationException { + + private InputStream inputStream; + private HttpURLConnection connection; + + public HttpClientTransportException() { + super(); + } + + public HttpClientTransportException(String message, HttpURLConnection conn, InputStream is) { + super(message); + connection = conn; + inputStream = is; + } + + public HttpClientTransportException(Throwable cause, HttpURLConnection conn, InputStream is) { + super(cause); + connection = conn; + inputStream = is; + } + + public HttpClientTransportException(String message, Throwable cause, HttpURLConnection conn, InputStream is) { + super(message, cause); + connection = conn; + inputStream = is; + } + + public InputStream getInputStream() { + return inputStream; + } + + public HttpURLConnection getConnection() { + return connection; + } +} diff --git a/src/main/java/com/salesforce/dataloader/exception/LoadExceptionOnServer.java b/src/main/java/com/salesforce/dataloader/exception/LoadExceptionOnServer.java new file mode 100644 index 000000000..d642d0f89 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/exception/LoadExceptionOnServer.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.exception; + +/** + * + * @author Lexi Viripaeff + * @since 6.0 + */ +@SuppressWarnings("serial") +public class LoadExceptionOnServer extends LoadException { + + public LoadExceptionOnServer() { + super(); + } + + public LoadExceptionOnServer(String message) { + super(message); + } + + public LoadExceptionOnServer(Throwable cause) { + super(cause); + } + + public LoadExceptionOnServer(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/exception/OAuthBrowserLoginRunnerException.java b/src/main/java/com/salesforce/dataloader/exception/OAuthBrowserLoginRunnerException.java new file mode 100644 index 000000000..2a4756a05 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/exception/OAuthBrowserLoginRunnerException.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.exception; + +@SuppressWarnings("serial") +public class OAuthBrowserLoginRunnerException extends Exception{ + + /** + * + */ + public OAuthBrowserLoginRunnerException() { + super(); + } + + /** + * @param message + */ + public OAuthBrowserLoginRunnerException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public OAuthBrowserLoginRunnerException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param cause + */ + public OAuthBrowserLoginRunnerException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/exception/RelationshipFormatException.java b/src/main/java/com/salesforce/dataloader/exception/RelationshipFormatException.java new file mode 100644 index 000000000..231f32fe0 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/exception/RelationshipFormatException.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.exception; + +/** + * ConfigInitializationException + * + * @author Alex Warshavsky + * @since 8.0 + */ +@SuppressWarnings("serial") +public class RelationshipFormatException extends Exception { + + /** + * + */ + public RelationshipFormatException() { + super(); + } + + /** + * @param message + */ + public RelationshipFormatException(String message) { + super(message); + } + + /** + * @param message + * @param cause + */ + public RelationshipFormatException(String message, Throwable cause) { + super(message, cause); + } + + /** + * @param cause + */ + public RelationshipFormatException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/com/salesforce/dataloader/install/Installer.java b/src/main/java/com/salesforce/dataloader/install/Installer.java new file mode 100644 index 000000000..92f420cc5 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/install/Installer.java @@ -0,0 +1,467 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.install; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FilenameFilter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.InvalidPathException; +import java.nio.file.NoSuchFileException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Map; + +import org.apache.commons.io.FileUtils; +import org.apache.logging.log4j.Level; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.util.AppUtil; + +public class Installer { + private static final String USERHOME=System.getProperty("user.home"); + private static final String PATH_SEPARATOR = System.getProperty("file.separator"); + private static final String CREATE_DEKSTOP_SHORTCUT_ON_WINDOWS = ":createDesktopShortcut"; + private static final String CREATE_START_MENU_SHORTCUT_ON_WINDOWS = ":createStartMenuShortcut"; + + private static Logger logger =DLLogManager.getLogger(Installer.class); + private static String[] OS_SPECIFIC_DL_COMMAND = {"dataloader.bat", "dataloader_console", "dataloader.sh"}; + + public static void install(Map argsmap) { + int exitCode = AppUtil.EXIT_CODE_NO_ERRORS; + boolean interactiveMode = true; + boolean installedInPreviousRun = false; + boolean skipCopyArtifacts = false; + try { + String installationFolder = "."; + installationFolder = new File(Installer.class.getProtectionDomain().getCodeSource().getLocation() + .toURI()).getParent(); + + for (String dlCmd : OS_SPECIFIC_DL_COMMAND) { + Path installFilePath = Paths.get(installationFolder + PATH_SEPARATOR + dlCmd); + if (Files.exists(installFilePath) && AppUtil.getAppRunMode() != AppUtil.APP_RUN_MODE.INSTALL) { + // installation completed + installedInPreviousRun = true; + return; + } + } + String installationFolderFromCommandLine = argsmap.get(AppConfig.CLI_OPTION_INSTALLATION_FOLDER_PROP); + boolean promptUserToDeleteExistingInstallationFolder = false; + if (installationFolderFromCommandLine == null || installationFolderFromCommandLine.isBlank()) { + skipCopyArtifacts = promptCurrentInstallationFolder(); + promptUserToDeleteExistingInstallationFolder = true; + interactiveMode = true; + } else { + interactiveMode = false; + promptUserToDeleteExistingInstallationFolder = false; + } + if (!skipCopyArtifacts) { + logger.debug("going to select installation folder"); + installationFolder = selectInstallationDir(installationFolderFromCommandLine); + logger.debug("going to copy artifacts"); + copyArtifacts(installationFolder, promptUserToDeleteExistingInstallationFolder); + } + extractInstallationArtifactsFromJar(installationFolder); + + String createDesktopShortcutStr = argsmap.get(AppConfig.CLI_OPTION_INSTALLATION_CREATE_DESKTOP_SHORTCUT_PROP); + logger.debug("going to create desktop shortcut"); + if (interactiveMode) { + createDesktopShortcut(installationFolder, true); + } else if (createDesktopShortcutStr != null + && ("true".equalsIgnoreCase(createDesktopShortcutStr) + || "yes".equalsIgnoreCase(createDesktopShortcutStr))) { + createDesktopShortcut(installationFolder, false); + } + + String createWindowsStartMenuShortcutStr = argsmap.get(AppConfig.CLI_OPTION_INSTALLATION_CREATE_WINDOWS_START_MENU_SHORTCUT_PROP); + logger.debug("going to create start menu shortcut"); + if (AppUtil.isRunningOnWindows()) { + if (interactiveMode) { + createStartMenuShortcut(installationFolder, true); + } else if (createWindowsStartMenuShortcutStr != null + && ("true".equalsIgnoreCase(createWindowsStartMenuShortcutStr) + || "yes".equalsIgnoreCase(createWindowsStartMenuShortcutStr))) { + createStartMenuShortcut(installationFolder, false); + } + } + + String createMacOSAppsFolderShortcutStr = argsmap.get(AppConfig.CLI_OPTION_INSTALLATION_CREATE_MACOS_APPS_FOLDER_SHORTCUT_PROP); + logger.debug("going to create start menu shortcut"); + if (AppUtil.isRunningOnMacOS()) { + if (interactiveMode) { + createAppsFolderShortcut(installationFolder, true); + } else if (createMacOSAppsFolderShortcutStr != null + && ("true".equalsIgnoreCase(createMacOSAppsFolderShortcutStr) + || "yes".equalsIgnoreCase(createMacOSAppsFolderShortcutStr))) { + createAppsFolderShortcut(installationFolder, false); + } + } + /* comment out auto-generation of list of properties at installation time + AppConfig appConfig = AppConfig.getInstance(null); + ConfigPropertyMetadata.printCSV(appConfig); + */ + } catch (Exception ex) { + handleException(ex, Level.FATAL); + exitCode = AppUtil.EXIT_CODE_CLIENT_ERROR; + } finally { + if (installedInPreviousRun || skipCopyArtifacts) { + return; + } + if (interactiveMode) { + System.out.print(Messages.getMessage(Installer.class, "exitMessage")); + try { + System.in.read(); + } catch (IOException e) { + // ignore + } + } + System.exit(exitCode); + } + } + + private static boolean promptCurrentInstallationFolder() throws IOException { + String currentExecutionFolder = AppUtil.getDirContainingClassJar(Installer.class); + return loopingYesNoPrompt(Messages.getMessage(Installer.class, "promptCurrentInstallationFolder", currentExecutionFolder)); + } + + private static String selectInstallationDir(String installationFolder) throws IOException { + if (installationFolder == null || installationFolder.isBlank()) { + installationFolder = ""; + System.out.println(Messages.getMessage(Installer.class, "initialMessage", USERHOME + PATH_SEPARATOR)); + String installationDirRoot = promptAndGetUserInput(Messages.getMessage(Installer.class, "promptInstallationFolder")); + if (installationDirRoot.isBlank()) { + installationDirRoot = "dataloader"; + } + logger.debug("installation folder: " + installationDirRoot); + String installationPathSuffix = installationDirRoot + PATH_SEPARATOR + "v" + AppUtil.DATALOADER_VERSION; + if (installationDirRoot.startsWith(PATH_SEPARATOR) + || (AppUtil.isRunningOnWindows() && installationDirRoot.indexOf(':') == 1 && installationDirRoot.indexOf(PATH_SEPARATOR) == 2)) { + // Absolute path specified. + // Absolute path on Mac and Linux start with PATH_SEPARATOR + // Absolute path on Windows starts with :\. For example, "C:\" + installationFolder = installationPathSuffix; + } else { + installationFolder = USERHOME + PATH_SEPARATOR + installationPathSuffix; + } + } + logger.debug("installation folder absolute path: " + installationFolder); + System.out.println(Messages.getMessage(Installer.class, "installationDirConfirmation", AppUtil.DATALOADER_VERSION, installationFolder)); + return installationFolder; + } + + private static void copyArtifacts(String installationDir, boolean promptUserToDeleteExistingInstallationFolder) throws Exception { + Path installationDirPath = Paths.get(installationDir); + if (Files.exists(installationDirPath)) { + boolean deleteExistingFolder = true; + if (promptUserToDeleteExistingInstallationFolder) { + final String prompt = Messages.getMessage(Installer.class, "overwriteInstallationDirPrompt", AppUtil.DATALOADER_VERSION, installationDir); + deleteExistingFolder = loopingYesNoPrompt(prompt); + } + if (deleteExistingFolder) { + System.out.println(Messages.getMessage(Installer.class, "deletionInProgressMessage", AppUtil.DATALOADER_VERSION)); + Messages.getMessage(Installer.class, "initialMessage"); + logger.debug("going to delete " + installationDir); + FileUtils.deleteDirectory(new File(installationDir)); + } else { + System.exit(AppUtil.EXIT_CODE_NO_ERRORS); + } + } + String installationSourceDir = "."; + installationSourceDir = new File(Installer.class.getProtectionDomain().getCodeSource().getLocation() + .toURI()).getParent(); + logger.debug("going to create " + installationDir); + createDir(installationDir); + logger.debug("going to copy contents of " + installationSourceDir + " to " + installationDir); + + String dataloaderJar = Installer.class.getProtectionDomain().getCodeSource().getLocation().getFile(); + if (dataloaderJar == null) { + logger.fatal("Did not find Data Loader jar in the installation artifacts. Unable to install Data Loader"); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); + } + FileUtils.copyFileToDirectory(new File(dataloaderJar), new File(installationDir)); + logger.debug("going to delete \\.* files from " + installationDir); + deleteFilesFromDir(installationDir, "\\.*"); + logger.debug("going to delete install.* files from " + installationDir); + deleteFilesFromDir(installationDir, "install.(.*)"); + logger.debug("going to delete META-INF from " + installationDir); + deleteFilesFromDir(installationDir, "META-INF"); + logger.debug("going to delete zip files from " + installationDir); + deleteFilesFromDir(installationDir, ".*.zip"); + } + + private static boolean loopingYesNoPrompt(String prompt) throws IOException { + for (;;) { + System.out.println(""); + String input = promptAndGetUserInput(prompt); + if (input == null || input.isBlank()) { + System.out.println(Messages.getMessage(Installer.class, "reprompt")); + } else if (Messages.getMessage(Installer.class, "promptAnswerYes").toLowerCase().startsWith(input.toLowerCase())) { + return true; + } else if (Messages.getMessage(Installer.class, "promptAnswerNo").toLowerCase().startsWith(input.toLowerCase())) { + return false; + } else { + System.out.println(Messages.getMessage(Installer.class, "reprompt")); + } + } + } + + private static String promptAndGetUserInput(String prompt) throws IOException { + if (prompt == null || prompt.isBlank()) { + prompt = "Provide input: "; + } + System.out.print(prompt); + BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); + String input = ""; + // Reading data using readLine + input = reader.readLine(); + return input; + } + + private static void deleteFilesFromDir(String folderName, String filePattern) throws IOException { + File folder = new File(folderName); + if (!folder.exists()) { + return; + } + final File[] files = folder.listFiles( new FilenameFilter() { + @Override + public boolean accept( final File dir, + final String name ) { + boolean match = name.matches(filePattern); + return match; + } + } ); + for ( final File file : files ) { + if (file.isDirectory()) { + FileUtils.deleteDirectory(file); + } else if ( !file.delete() ) { + logger.error("Can't remove " + file.getAbsolutePath()); + } + } + } + + interface ShortcutCreatorInterface { + public void create() throws Exception; + } + + private static void createShortcut(String prompt, ShortcutCreatorInterface shortcutCreator, String success) { + for (;;) { + System.out.println(""); + String input = ""; + if (prompt == null) { // do not prompt, create the shortcut + input = Messages.getMessage(Installer.class, "promptAnswerYes").toLowerCase(); + } else { + try { + input = promptAndGetUserInput(prompt); + } catch (IOException e) { + logger.error(Messages.getMessage(Installer.class, "responseReadError")); + handleException(e, Level.ERROR); + } + } + if (input == null || input.isBlank()) { + System.out.println(Messages.getMessage(Installer.class, "reprompt")); + } else if (Messages.getMessage(Installer.class, "promptAnswerYes").toLowerCase().startsWith(input.toLowerCase())) { + try { + shortcutCreator.create(); + if (success != null && !success.isBlank()) { + System.out.println(success); + } + } catch (Exception ex) { + logger.error(Messages.getMessage(Installer.class, "shortcutCreateError")); + handleException(ex, Level.ERROR); + } + break; + } else if (Messages.getMessage(Installer.class, "promptAnswerNo").toLowerCase().startsWith(input.toLowerCase())) { + return; + } else { + System.out.println(Messages.getMessage(Installer.class, "reprompt")); + } + } + } + + private static void createDesktopShortcut(String installationDir, boolean isPromptNeeded) { + final String PROMPT = Messages.getMessage(Installer.class, "createDesktopShortcutPrompt"); + final String SUCCESS = Messages.getMessage(Installer.class, "successCreateDesktopShortcut"); + if (AppUtil.isRunningOnWindows()) { + createShortcut(isPromptNeeded ? PROMPT : null, + new ShortcutCreatorInterface() { + public void create() throws Exception { + createShortcutOnWindows(CREATE_DEKSTOP_SHORTCUT_ON_WINDOWS, installationDir); + } + }, SUCCESS); + } else if (AppUtil.isRunningOnMacOS()) { + createShortcut(isPromptNeeded ? PROMPT : null, + new ShortcutCreatorInterface() { + public void create() throws Exception { + createSymLink(USERHOME + "/Desktop/DataLoader " + AppUtil.DATALOADER_VERSION, + installationDir + "/dataloader.app", true); + } + }, SUCCESS); + } + } + + private static void createAppsFolderShortcut(String installationDir, boolean isPromptNeeded) { + final String PROMPT = Messages.getMessage(Installer.class, "createApplicationsDirShortcutPrompt"); + final String SUCCESS = Messages.getMessage(Installer.class, "successCreateApplicationsDirShortcut"); + + if (AppUtil.isRunningOnMacOS()) { + createShortcut(isPromptNeeded ? PROMPT : null, + new ShortcutCreatorInterface() { + public void create() throws Exception { + createSymLink("/Applications/DataLoader " + AppUtil.DATALOADER_VERSION, + installationDir + "/dataloader.app", true); + } + }, SUCCESS); + } + } + + private static void createStartMenuShortcut(String installationDir, boolean isPromptNeeded) { + final String PROMPT = Messages.getMessage(Installer.class, "createStartMenuShortcutPrompt"); + final String SUCCESS = Messages.getMessage(Installer.class, "successCreateStartMenuShortcut"); + + if (AppUtil.isRunningOnWindows()) { + createShortcut(isPromptNeeded ? PROMPT : null, + new ShortcutCreatorInterface() { + public void create() throws Exception { + String APPDATA = System.getenv("APPDATA"); + String SALESFORCE_START_MENU_DIR = APPDATA + "\\Microsoft\\Windows\\Start Menu\\Programs\\Salesforce\\" ; + createDir(SALESFORCE_START_MENU_DIR); + createShortcutOnWindows(CREATE_START_MENU_SHORTCUT_ON_WINDOWS, installationDir); + } + }, SUCCESS); + } + } + + private static void createSymLink(String symlink, String target, boolean deleteExisting) throws IOException { + Path symlinkPath = Paths.get(symlink); + if (deleteExisting) { + logger.debug("Deleting existing symlink " + symlink); + try { + Files.delete(symlinkPath); + } catch (NoSuchFileException x) { + logger.debug("symlink " + symlink + " does not exist"); + } catch (IOException ex) { + // File permission problems are caught here. + logger.warn(ex.getMessage()); + } + } else if (Files.exists(symlinkPath)) { + logger.debug("Symlink " + symlink + " exists. Skipping linking it to " + target); + return; + } + logger.debug("going to create symlink: " + symlink + " pointing to " + target); + Files.createSymbolicLink(symlinkPath, Paths.get(target)); + } + + private static void createShortcutOnWindows(final String shortcutCommand, String installationDir) throws IOException, InterruptedException { + ArrayList cmd = new ArrayList(); + cmd.add("cmd"); + cmd.add("/c"); + cmd.add("call"); + cmd.add("\"" + installationDir + "\\util\\util.bat\""); + cmd.add(shortcutCommand); + cmd.add("\"" + installationDir + "\""); + int exitVal = AppUtil.exec(cmd, null); + logger.debug("windows command exited with exit code: " + exitVal); + } + + private static void configureOSSpecificInstallationArtifactsPostCopy(String installationDir) throws IOException { + if (AppUtil.isRunningOnWindows()) { + configureWindowsArtifactsPostCopy(installationDir); + } else if (AppUtil.isRunningOnMacOS()) { + configureMacOSArtifactsPostCopy(installationDir); + } else if (AppUtil.isRunningOnLinux()) { + configureLinuxArtifactsPostCopy(installationDir); + } + } + + private static void configureMacOSArtifactsPostCopy(String installationDir) throws IOException { + final String MACOS_PACKAGE_BASE = installationDir + "/dataloader.app/Contents"; + final String PATH_TO_DL_EXECUTABLE_ON_MAC = MACOS_PACKAGE_BASE + "/MacOS/dataloader"; + + // delete unnecessary artifacts + logger.debug("going to delete dataloader.ico from " + installationDir); + deleteFilesFromDir(installationDir + "/util", "(.*).bat"); + + // create a soft link from /dataloader.app/Contents/MacOS/dataloader to + // /dataloader_console + logger.debug("going to create symlink from " + + + PATH_TO_DL_EXECUTABLE_ON_MAC + + " to " + + installationDir + "/dataloader_console"); + logger.debug("going to create " + MACOS_PACKAGE_BASE + "/MacOS"); + createDir(MACOS_PACKAGE_BASE + "/MacOS"); + createSymLink(PATH_TO_DL_EXECUTABLE_ON_MAC, + installationDir + "/dataloader_console", true); + } + + private static void configureWindowsArtifactsPostCopy(String installationDir) throws IOException { + deleteFilesFromDir(installationDir + "/util", "(.*).sh"); + } + + private static void configureLinuxArtifactsPostCopy(String installationDir) throws IOException { + try { + if (Files.exists(Paths.get(installationDir + "/dataloader_console"))) { + Files.move(Paths.get(installationDir + "/dataloader_console"), + Paths.get(installationDir + "/dataloader.sh")); + } + } catch (InvalidPathException ex) { + // do nothing - dataloader_console not found in the path + } + deleteFilesFromDir(installationDir + "/util", "(.*).bat"); + } + + private static void createDir(String dirPath) throws IOException { + Files.createDirectories(Paths.get(dirPath)); + } + + public static void extractInstallationArtifactsFromJar(String installationDir) throws URISyntaxException, IOException { + AppUtil.extractDirFromJar("samples", installationDir, false); + AppUtil.extractDirFromJar("configs", installationDir, false); + String osSpecificExtractionPrefix = "mac/"; + if (AppUtil.isRunningOnWindows()) { + osSpecificExtractionPrefix = "win/"; + } else if (AppUtil.isRunningOnLinux()) { + osSpecificExtractionPrefix = "linux/"; + } + AppUtil.extractDirFromJar(osSpecificExtractionPrefix, installationDir, true); + configureOSSpecificInstallationArtifactsPostCopy(installationDir); + } + + private static void handleException(Throwable ex, Level level) { + if (logger != null) { + logger.log(level, "Installer :", ex); + } else { + ex.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/mapping/CaseInsensitiveSet.java b/src/main/java/com/salesforce/dataloader/mapping/CaseInsensitiveSet.java deleted file mode 100644 index 421a9b748..000000000 --- a/src/main/java/com/salesforce/dataloader/mapping/CaseInsensitiveSet.java +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.mapping; - -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; - -public class CaseInsensitiveSet { - private final HashMap originalMap; - - public CaseInsensitiveSet(){ - originalMap = new HashMap(); - } - - public CaseInsensitiveSet(Set values){ - this(); - for(String value: values){ - add(value); - } - } - - public void add(String value) { - String lowerCase = value.toLowerCase(); - originalMap.put(lowerCase, value); - } - - public boolean contains(String value) { - return value != null ? originalMap.containsKey(value.toLowerCase()): false; - - } - - public String getOriginal(String value){ - return contains(value) ? (String) originalMap.get(value.toLowerCase()): value; - } - - public boolean isEmpty(){ - return originalMap.isEmpty(); - } - - public Set getOriginalValues() { - return new HashSet(originalMap.values()); - } -} diff --git a/src/main/java/com/salesforce/dataloader/mapping/LoadMapper.java b/src/main/java/com/salesforce/dataloader/mapping/LoadMapper.java index dc15219c5..68f77f7d2 100644 --- a/src/main/java/com/salesforce/dataloader/mapping/LoadMapper.java +++ b/src/main/java/com/salesforce/dataloader/mapping/LoadMapper.java @@ -26,16 +26,27 @@ package com.salesforce.dataloader.mapping; -import java.util.*; -import java.util.Map.Entry; - import com.salesforce.dataloader.client.PartnerClient; import com.salesforce.dataloader.exception.MappingInitializationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.soap.partner.Field; -import org.apache.log4j.Logger; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; +import org.springframework.util.LinkedCaseInsensitiveMap; import org.springframework.util.StringUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.Map.Entry; +import java.util.Set; + /** * Mapper which maps field names for loading operations. Field names are mapped from dao (local) name to sfdc name. * @@ -44,8 +55,10 @@ */ public class LoadMapper extends Mapper { - private static final Logger logger = Logger.getLogger(Mapper.class); - + private static final Logger logger = DLLogManager.getLogger(Mapper.class); + private TableHeader localCompositeRowHeader = null; + private TableHeader sfdcRowHeader = null; + public LoadMapper(PartnerClient client, Collection columnNames, Field[] fields, String mappingFileName) throws MappingInitializationException { super(client, columnNames, fields, mappingFileName); @@ -61,23 +74,113 @@ protected void putPropertyEntry(Entry entry) { } public Map getMappingWithUnmappedColumns(boolean includeUnmapped) { - final Map result = new HashMap(getMap()); - if (includeUnmapped) { - for (String daoColumn : getDaoColumns()) { - if (getMapping(daoColumn) == null) result.put(daoColumn, null); + final Map result = new LinkedCaseInsensitiveMap(); + Collection candidateCols = getCompositeDAOColumns(); + if (candidateCols == null || candidateCols.isEmpty()) { + // no compositions yet + candidateCols = this.getDaoColumns(); + } + // get mappings in the same order as DAO column order + for (String daoColumn : candidateCols) { + String mapping = getMapping(daoColumn, false, true); + if (includeUnmapped || mapping != null) { + result.put(daoColumn, mapping); + } + } + + // Make sure to not miss existing mappings even if they are not in DAO. + for (Map.Entry currentMapEntry : getMap().entrySet()) { + if (!result.containsKey(currentMapEntry.getKey())) { + result.put(currentMapEntry.getKey(), currentMapEntry.getValue()); } } return result; } - public Row mapData(Row localRow) { - Row mappedData = new Row(); - for (Map.Entry entry : localRow.entrySet()) { - String sfdcName = getMapping(entry.getKey()); - if (StringUtils.hasText(sfdcName)) { - mappedData.put(sfdcName, entry.getValue()); + private void initializeHeaders() { + Map currentMappings = getMappingWithUnmappedColumns(false); + this.localCompositeRowHeader = new TableHeader(new ArrayList(currentMappings.keySet())); + ArrayList sfdcFieldList = new ArrayList(); + for (String localCompositeCol : currentMappings.keySet()) { + String sfdcNameList = getMapping(localCompositeCol, true, true); + if (StringUtils.hasText(sfdcNameList)) { + String sfdcNameArray[] = sfdcNameList.split(AppUtil.COMMA); + for (String sfdcName : sfdcNameArray) { + sfdcFieldList.add(sfdcName.trim()); + } + } + } + Map constantsMap = getConstantsMap(); + for (String sfdcNameList : constantsMap.keySet()) { + String sfdcNameArray[] = sfdcNameList.split(AppUtil.COMMA); + for (String sfdcName : sfdcNameArray) { + sfdcFieldList.add(sfdcName.trim()); + } + } + this.sfdcRowHeader = new TableHeader(sfdcFieldList); + } + + public TableRow mapData(TableRow localRow, boolean firstRow) { + if (firstRow) { + initializeHeaders(); + } + Set compositeDAOCols = this.getCompositeDAOColumns(); + HashMap compositeColValueMap = new HashMap(); + HashMap compositeColSizeMap = this.getCompositeColSizeMap(); + for (String compositeCol : compositeDAOCols) { + Integer compositeColSize = compositeColSizeMap.get(compositeCol); + if (compositeColSize == null) { + logger.warn("Invalid composite column : " + compositeCol); + StringTokenizer st = new StringTokenizer(compositeCol, AppUtil.COMMA); + compositeColSize = Integer.valueOf(st.countTokens()); + } + compositeColValueMap.put(compositeCol, new Object[compositeColSize]); + } + + HashMap daoColPositionMap = this.getDaoColPositionInCompositeColMap(); + HashMap daoColToCompositeColMap = this.getDaoColToCompositeColMap(); + for (String daoCol : localRow.getHeader().getColumns()) { + String compositeColName = daoColToCompositeColMap.get(daoCol); + if (compositeColName == null) { + continue; // DAO column is not mapped + } + Object[] compositeColValueArray = compositeColValueMap.get(compositeColName); + Integer positionInCompositeCol = daoColPositionMap.get(daoCol); + Object daoColVal = localRow.get(daoCol); + if (compositeColValueArray.length > 1 + && daoColVal != null + && !daoColVal.getClass().equals(String.class)) { + // composite DAO column has a non-String class. Ignore composition + if (positionInCompositeCol == 0) { + compositeColValueArray[0] = daoColVal; + } else { + daoColToCompositeColMap.remove(daoCol); + daoColPositionMap.remove(daoCol); + } + } else { // dao column value is of type String + compositeColValueArray[positionInCompositeCol] = daoColVal; + } + } + + TableRow localCompositeRow = new TableRow(this.localCompositeRowHeader); + for (String compositeCol : compositeDAOCols) { + Object[] compositeColValueArray = compositeColValueMap.get(compositeCol); + Object compositeColValue = compositeColValueArray[0]; + for (int i = 1; i < compositeColValueArray.length; i++) { + compositeColValue += AppUtil.COMMA + " " + compositeColValueArray[i]; + } + localCompositeRow.put(compositeCol, compositeColValue); + } + TableRow mappedData = new TableRow(this.sfdcRowHeader); + for (String localCompositeRowElement : localCompositeRowHeader.getColumns()) { + String sfdcNameList = getMapping(localCompositeRowElement, true, true); + if (StringUtils.hasText(sfdcNameList)) { + String sfdcNameArray[] = sfdcNameList.split(AppUtil.COMMA); + for (String sfdcName : sfdcNameArray) { + mappedData.put(sfdcName.trim(), localCompositeRow.get(localCompositeRowElement)); + } } else { - logger.info("Mapping for field " + entry.getKey() + " will be ignored since destination column is empty"); + logger.info("Mapping for field " + localCompositeRowElement + " will be ignored since destination column is empty"); } } mapConstants(mappedData); @@ -86,13 +189,46 @@ public Row mapData(Row localRow) { public void verifyMappingsAreValid() throws MappingInitializationException { for (Map.Entry entry : getMappingWithUnmappedColumns(false).entrySet()) { - String sfdcName = entry.getValue(); - if(StringUtils.hasText(sfdcName)) { - final Field f = getClient().getField(sfdcName); - if (f == null) - throw new MappingInitializationException("Field mapping is invalid: " + entry.getKey() + " => " + sfdcName); + String sfdcNameList = entry.getValue(); + if(StringUtils.hasText(sfdcNameList)) { + String sfdcNameArray[] = sfdcNameList.split(AppUtil.COMMA); + for (String sfdcName : sfdcNameArray) { + final Field f = getClient().getField(sfdcName.trim()); + if (f == null) + throw new MappingInitializationException("Field mapping is invalid: " + entry.getKey() + " => " + sfdcName); + } } } } - -} + + public List getMappedDaoColumns() { + Map possibleMappings = this.getMappingWithUnmappedColumns(true); + ArrayList mappedColList = new ArrayList(); + for (String daoCol : possibleMappings.keySet()) { + String mappedName = this.map.get(daoCol); + if (mappedName != null) { + if (mappedName.contains(AppUtil.COMMA)) { + String[] mappedNameList = mappedName.split(AppUtil.COMMA); + for (int i=0; i constants = caseInsensitiveMap(); + protected final Map daoColumnNames = new LinkedCaseInsensitiveMap(); + + private LinkedHashMap compositeColSizeMap = new LinkedHashMap(); + private LinkedHashMap daoColPositionInCompositeColMap = new LinkedHashMap(); + private LinkedHashMap daoColToCompositeColMap = new LinkedHashMap(); + private LinkedHashMap fieldTypeIsStringMap = new LinkedHashMap(); + private final Map constants = new LinkedCaseInsensitiveMap(); - private final Map map = caseInsensitiveMap(); + protected final Map map = new LinkedCaseInsensitiveMap(); private final PartnerClient client; - private final CaseInsensitiveSet fields; - - private Map caseInsensitiveMap() { - return new TreeMap(String.CASE_INSENSITIVE_ORDER); - } + private final Map fields = new LinkedCaseInsensitiveMap(); + protected final String mappingFileName; protected Mapper(PartnerClient client, Collection columnNames, Field[] fields, String mappingFileName) throws MappingInitializationException { this.client = client; - this.fields = new CaseInsensitiveSet(); - Set daoColumns = new TreeSet(String.CASE_INSENSITIVE_ORDER); - if (columnNames != null) daoColumns.addAll(columnNames); - this.daoColumns = new CaseInsensitiveSet(Collections.unmodifiableSet(daoColumns)); - putPropertyFileMappings(mappingFileName); + if (columnNames != null) { + int i = 0; + for (String colName : columnNames) { + i++; + if (colName == null) { + String errorMsg = "Missing column name in the CSV file at column " + i; + logger.error(errorMsg); + throw new MappingInitializationException(errorMsg); + } + daoColumnNames.put(colName, colName); + daoColPositionInCompositeColMap.put(colName, 0); + daoColToCompositeColMap.put(colName, colName); + compositeColSizeMap.put(colName, 1); + } + } if (fields != null) { for (Field field : fields) { - this.fields.add(field.getName()); + boolean isStringType = true; + this.fields.put(field.getName(), field.getName()); + FieldType fieldType = field.getType(); + if (fieldType == FieldType.string + || fieldType == FieldType.textarea) { + isStringType = true; + } else { + isStringType = false; + } + this.fieldTypeIsStringMap.put(field.getName().toLowerCase(), isStringType); } } + this.mappingFileName = mappingFileName; + putPropertyFileMappings(mappingFileName); } - public final void putMapping(String src, String dest) { - this.map.put(daoColumns.getOriginal(src), fields.getOriginal(dest)); + public final void putMapping(String src, String destList) { + processCompositeDaoColName(src, destList); + + // destination can be multiple field names for upload operations + StringTokenizer st = new StringTokenizer(destList, AppUtil.COMMA); + String originalDestList = null; + while(st.hasMoreElements()) { + String v = st.nextToken(); + v = v.trim(); + String originalVal = fields.get(v); + if (originalVal == null) { + originalVal = v; + // check if it is a lookup relationship field + if (v.contains(ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR) + || v.contains(ParentSObjectFormatter.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR)) { + try { + Field lookupFieldInParent = client.getField(v); + ParentIdLookupFieldFormatter specifiedFieldFormatter = new ParentIdLookupFieldFormatter(v); + Field relationshipField = client.getFieldFromRelationshipName(specifiedFieldFormatter.getParent().getRelationshipName()); + String parentSObjectName = specifiedFieldFormatter.getParent().getParentObjectName(); + ReferenceEntitiesDescribeMap refEntitiesMap = client.getReferenceDescribes(); + DescribeRefObject refObject = refEntitiesMap.getParentSObject(specifiedFieldFormatter.getParent().toString()); + if (refObject == null) { // legacy format: : + if (relationshipField != null) { + parentSObjectName = relationshipField.getReferenceTo()[0]; + } + } else { // new format: :- + parentSObjectName = client.describeSObject(refObject.getParentObjectName()).getName(); + } + if (relationshipField != null) { + ParentIdLookupFieldFormatter sfFieldFormatter = new ParentIdLookupFieldFormatter(parentSObjectName, + relationshipField.getRelationshipName(), + lookupFieldInParent.getName()); + originalVal = sfFieldFormatter.toString(); + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + if (originalDestList == null) { + originalDestList = originalVal; + } else { + originalDestList = originalDestList + ", " + originalVal; + } + } + this.map.put(src, originalDestList); + } + + private void processCompositeDaoColName(String mappingSrcStr, String destFieldList) { + boolean isDestinationListStringOnly = true; + StringTokenizer st = new StringTokenizer(destFieldList, AppUtil.COMMA); + while(st.hasMoreElements()) { + String destFieldName = st.nextToken(); + destFieldName = destFieldName.trim(); + Boolean destFieldTypeIsString = this.fieldTypeIsStringMap.get(destFieldName.toLowerCase()); + if (destFieldTypeIsString == null || !destFieldTypeIsString) { + isDestinationListStringOnly = false; + logger.debug(destFieldName + " is not string or text area."); + break; + } + } + + int daoColCount = 0; + st = new StringTokenizer(mappingSrcStr, AppUtil.COMMA); + while(st.hasMoreElements()) { + String mappingSrcCol = st.nextToken(); + mappingSrcCol = mappingSrcCol.trim(); + String daoCol = daoColumnNames.get(mappingSrcCol); + if (daoCol == null) { + daoCol = mappingSrcCol; + } + daoColPositionInCompositeColMap.put(daoCol, daoColCount); + if (daoColCount == 0 && st.countTokens() == 0 && daoCol.equalsIgnoreCase(mappingSrcCol)) { + // keep dao column's label if possible + daoColToCompositeColMap.put(daoCol, daoCol); + } else { + daoColToCompositeColMap.put(daoCol, mappingSrcStr); + } + daoColCount++; + if (!isDestinationListStringOnly) { + // set up only the first daoCol for mapping if the destination + // field list contains a field whose type is not string + logger.debug("Using only the first CSV column '" + daoCol + "' for mapping because one of the sobject fields it is mapped to is not a string or a text area"); + break; + } + } + if (daoColCount == 0) { + String daoCol = daoColumnNames.get(mappingSrcStr); + if (daoCol == null) { + daoCol = mappingSrcStr; + } + getDaoColToCompositeColMap().put(mappingSrcStr, mappingSrcStr); + daoColCount = 1; + } + compositeColSizeMap.put(mappingSrcStr, daoColCount); } protected void putConstant(String name, String value) { @@ -103,7 +238,7 @@ protected void putConstant(String name, String value) { } private void handleMultipleValuesFromConstant(String name, String value) { - StringTokenizer st = new StringTokenizer(name, ","); + StringTokenizer st = new StringTokenizer(name, AppUtil.COMMA); while(st.hasMoreElements()) { String v = st.nextToken(); v = v.trim(); @@ -115,12 +250,14 @@ private static String extractConstant(String constantVal) { return constantVal.substring(1, constantVal.length() - 1); } - protected void mapConstants(Row rowMap) { - rowMap.putAll(this.constants); + protected void mapConstants(TableRow row) { + for (String constKey : constants.keySet()) { + row.put(constKey, constants.get(constKey)); + } } private Properties loadProperties(String fileName) throws MappingInitializationException { - Properties props = new Properties(); + OrderedProperties props = new OrderedProperties(); if (fileName != null && fileName.length() > 0) { try { FileInputStream in = new FileInputStream(fileName); @@ -143,6 +280,7 @@ public void putPropertyFileMappings(String fileName) throws MappingInitializatio } public void putPropertyFileMappings(Properties props) { + clearMappings(); for (Entry entry : props.entrySet()) { putPropertyEntry(entry); } @@ -155,7 +293,7 @@ public void putPropertyFileMappings(Properties props) { protected abstract void putPropertyEntry(Entry entry); protected boolean hasDaoColumns() { - return !this.daoColumns.isEmpty(); + return !this.daoColumnNames.isEmpty(); } protected static boolean isConstant(String name) { @@ -173,26 +311,49 @@ public Collection getDestColumns() { return this.map.values(); } - public String getMapping(String srcName) { - if(map.containsKey(srcName)) { - return map.get(srcName); + /* + public String getMapping(String srcName, boolean isSrcNameComposite) { + return getMapping(srcName, false, isSrcNameComposite); + } + */ + + public String getMapping(String srcName, boolean strictMatching, boolean isSrcNameComposite) { + String compositeColName = null; + if (isSrcNameComposite) { + compositeColName = srcName; + } else { + compositeColName = this.getDaoColToCompositeColMap().get(srcName); + } + if (compositeColName == null) { + // DAO column is not mapped, ignore + return null; + } + if (map.containsKey(compositeColName)) { + return map.get(compositeColName); } // handle aggregate queries - for(Entry entry: map.entrySet()) { - if(entry.getKey().endsWith("." + srcName)) { - return entry.getValue(); + if (!strictMatching) { + for (Entry entry : map.entrySet()) { + if (entry.getKey().endsWith("." + srcName)) { + return entry.getValue(); + } } } return null; } - - public void clearMap() { + public void clearMappings() { this.map.clear(); } public void save(String filename) throws IOException { if (filename == null) throw new IOException(Messages.getMessage(getClass(), "errorFileName")); Properties props = new Properties(); + for (String key : map.keySet()) { + String value = map.get(key); + if (value == null) { + map.put(key, ""); + } + } props.putAll(this.map); FileOutputStream out = new FileOutputStream(filename); try { @@ -203,7 +364,7 @@ public void save(String filename) throws IOException { } public boolean hasDaoColumn(String localName) { - return this.daoColumns.contains(localName); + return this.daoColumnNames.containsKey(localName); } public void removeMapping(String srcName) { @@ -218,11 +379,29 @@ public Map getConstantsMap() { return Collections.unmodifiableMap(this.constants); } - protected Set getDaoColumns() { - return this.daoColumns.getOriginalValues(); + protected Collection getDaoColumns() { + return this.daoColumnNames.values(); } public PartnerClient getClient() { return client; } -} + + protected Set getCompositeDAOColumns() { + LinkedHashSet compositeDAOCols = new LinkedHashSet(); + compositeDAOCols.addAll(this.daoColToCompositeColMap.values()); + return compositeDAOCols; + } + + protected HashMap getCompositeColSizeMap() { + return compositeColSizeMap; + } + + protected HashMap getDaoColPositionInCompositeColMap() { + return daoColPositionInCompositeColMap; + } + + protected HashMap getDaoColToCompositeColMap() { + return daoColToCompositeColMap; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/mapping/SOQLInfo.java b/src/main/java/com/salesforce/dataloader/mapping/SOQLInfo.java index b540fb7ac..916b3b4b3 100644 --- a/src/main/java/com/salesforce/dataloader/mapping/SOQLInfo.java +++ b/src/main/java/com/salesforce/dataloader/mapping/SOQLInfo.java @@ -29,12 +29,15 @@ import java.util.*; import java.util.concurrent.atomic.AtomicInteger; +import com.salesforce.dataloader.util.AppUtil; + /** * Class to parse information used by DataLoader from a soql expression * * @author Colin Jarvis * @since 21.0 */ +@SuppressWarnings("serial") class SOQLInfo { static class SOQLParserException extends Exception { @@ -52,7 +55,7 @@ static class SOQLFieldInfo { fieldString = getTrimmed(fieldString); int lparenIdx = fieldString.indexOf('('); // no nested queries! - if (lparenIdx == 0) throw invalidSoql("Nested queries are not supported"); + if (lparenIdx == 0) throw invalidSoql("Nested queries are not supported in SOQL SELECT clause"); if (lparenIdx < 0) { // normal field this.fieldName = fieldString; @@ -135,7 +138,7 @@ public String toString() { String rawFields = soql.substring(SELECT_KEYWORD.length(), fromIdx).trim(); AtomicInteger aggIdx = new AtomicInteger(); - for (String fieldString : rawFields.split(",")) { + for (String fieldString : rawFields.split(AppUtil.COMMA)) { SOQLFieldInfo soqlFieldInfo = new SOQLFieldInfo(fieldString, aggIdx); this.selectedFields.add(soqlFieldInfo); } diff --git a/src/main/java/com/salesforce/dataloader/mapping/SOQLMapper.java b/src/main/java/com/salesforce/dataloader/mapping/SOQLMapper.java index 3cc41ed78..b7ed2626f 100644 --- a/src/main/java/com/salesforce/dataloader/mapping/SOQLMapper.java +++ b/src/main/java/com/salesforce/dataloader/mapping/SOQLMapper.java @@ -26,22 +26,36 @@ package com.salesforce.dataloader.mapping; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.Map.Entry; - -import com.salesforce.dataloader.model.Row; -import org.apache.log4j.Logger; - import com.salesforce.dataloader.client.PartnerClient; import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.mapping.SOQLInfo.SOQLFieldInfo; import com.salesforce.dataloader.mapping.SOQLInfo.SOQLParserException; -import com.sforce.soap.partner.*; +import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; +import com.sforce.soap.partner.DescribeSObjectResult; +import com.sforce.soap.partner.Field; +import com.sforce.soap.partner.FieldType; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; import com.sforce.ws.bind.XmlObject; +import org.apache.logging.log4j.Logger; +import org.springframework.util.LinkedCaseInsensitiveMap; +import com.salesforce.dataloader.util.DLLogManager; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TimeZone; +import java.util.TreeMap; + import javax.xml.namespace.QName; /** @@ -50,11 +64,34 @@ * @author Colin Jarvis * @since 21.0 */ + +/* + * Mapping for extraction operations has the following parts: + * 1. A mapping file provided in the constructor. Mapper.map stores its mappings. + * 2. A mapping array provided in the constructor. Mapper.daoColumns field stores this array. + * 3. SoQL - fields specified in the query. This is skipped if skipSoQLMapping == true in the constructor. + * 4. query results - fields returned in the results. This is skipped if skipSoQLMapping == false. + * + * The implementation needs to do the following: + * If skipSoQLMapping == false (default case, legacy behavior) + * start with 1, overlay 2, produce an output file containing fields from the union of 1, 2, and 3. + * If skipSoQLMapping == true (no client-side SoQL checks, no client-side parsing of SoQL query) + * produce an output file containing all fields in the result. Use mapped field name if the field is mapped. + * + * map field defined in Mapper class stores mapping set in 1 and 2. + * soqlMap field defined in SOQLMapper class stores 1, 2, and 3 or 1, 2, and 4 in initSoQLMapping() as follows: + * - Construct a map based on SoQL query. It maps entity field names to aliases specified in SoQL by invoking addSOQLFieldMapping(). + * e.g. if the query is 'select a.name from Account a', soqlMap will contain "name":"a.name" as an entry. + * + * - if there are mappings from 1 and 2, get the list of soql query fields and recreate soqlMap. + */ public class SOQLMapper extends Mapper { - private static final Logger logger = Logger.getLogger(SOQLMapper.class); + private static final Logger logger = DLLogManager.getLogger(SOQLMapper.class); private SOQLInfo soqlInfo; + private Map extractionMap = new LinkedCaseInsensitiveMap(); + private boolean isInitialized = false; public SOQLMapper(PartnerClient client, Collection columnNames, Field[] fields, String mappingFileName) throws MappingInitializationException { @@ -62,28 +99,48 @@ public SOQLMapper(PartnerClient client, Collection columnNames, Field[] } public List getDaoColumnsForSoql() { + Collection daoColumnsCollection = this.extractionMap.values(); List daoColumns = new ArrayList(); - for (SOQLFieldInfo fieldInfo : soqlInfo.getSelectedFields()) { - String daoColumn = getMapping(normalizeSoql(fieldInfo)); - if (daoColumn != null) daoColumns.add(daoColumn); + for (String daoCol : daoColumnsCollection) { + daoColumns.add(daoCol); } return daoColumns; } public Row mapPartnerSObjectSfdcToLocal(SObject sobj) { - Row map = new Row(); - mapPartnerSObject(map, "", sobj); - mapConstants(map); - return map; + Row row = new Row(); + mapPartnerSObject(row, "", sobj); + mapConstants(row); + return row; + } + + // overwrite parent's methods to use soqlMap instead of map + public String getExtractionMapping(String srcName, boolean strictMatching) { + if (extractionMap.containsKey(srcName)) { + return extractionMap.get(srcName); + } + // handle aggregate queries + if (!strictMatching) { + for (Entry entry : extractionMap.entrySet()) { + if (entry.getKey().equalsIgnoreCase(srcName) + || entry.getKey().toLowerCase().endsWith("." + srcName.toLowerCase())) { + return entry.getValue(); + } + } + } + return null; } - private void mapPartnerSObject(Row map, String prefix, XmlObject sobj) { + private void mapPartnerSObject(Row row, String prefix, XmlObject sobj) { Iterator fields = sobj.getChildren(); if (fields == null) return; while (fields.hasNext()) { XmlObject field = fields.next(); final String fieldName = prefix + field.getName().getLocalPart(); - String localName = getMapping(fieldName); + String localName = getExtractionMapping(fieldName, false); + if (localName == null) { + localName = fieldName; + } if (localName != null) { Object value = field.getValue(); QName xmlType = field.getXmlType(); @@ -91,25 +148,31 @@ private void mapPartnerSObject(Row map, String prefix, XmlObject sobj) { //WSC got confused and converted a date string to a date object. //this causes weirdness in the output format and timezone correction that we don't want //convert the type back to a string before a later handler mis-handles it - SimpleDateFormat formatter = new SimpleDateFormat("YYYY-MM-dd"); + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); + formatter.setTimeZone(TimeZone.getTimeZone("GMT")); value = formatter.format(value); } - map.put(localName, value); + row.put(localName, value); } - mapPartnerSObject(map, fieldName + ".", field); + mapPartnerSObject(row, fieldName + ".", field); } } @Override protected void putPropertyEntry(Entry entry) { - String dao = (String)entry.getValue(); - String sfdc = (String)entry.getKey(); - if (isConstant(sfdc)) - putConstant(dao, sfdc); - else if (!hasDaoColumns() || hasDaoColumn(dao)) try { - addSoqlFieldMapping((String)entry.getValue(), new SOQLFieldInfo(sfdc)); - } catch (SOQLParserException e) { - throw new InvalidMappingException(e.getMessage(), e); + String daoColName = (String)entry.getValue(); + String sfdcColName = (String)entry.getKey(); + if (isConstant(sfdcColName)) { + putConstant(daoColName, sfdcColName); + } else if (!hasDaoColumns() || hasDaoColumn(daoColName)) { + try { + addExtractionMapping((String)entry.getValue(), new SOQLFieldInfo(sfdcColName)); + } catch (SOQLParserException e) { + throw new InvalidMappingException(e.getMessage(), e); + } catch (InvalidMappingException e) { + logger.warn("Unable to find Salesforce object field " + sfdcColName + " specified in the mapping file " + this.mappingFileName); + logger.warn(e.getMessage()); + } } } @@ -117,67 +180,153 @@ public Row mapCsvRowSfdcToLocal(List headers, List values, Strin Row resultRow = new Row(); Iterator headerIter = headers.listIterator(); for (String val : values) { - String sfdcName = headerIter.next(); - if ("Id".equalsIgnoreCase(sfdcName)) id.append(val); - String localName = getMapping(sfdcName); - if (localName == null) { - logger.warn("sfdc returned row that cannot be mapped: " + sfdcName); - } else { - resultRow.put(localName, val); + String sfdcFieldName = headerIter.next(); + if ("Id".equalsIgnoreCase(sfdcFieldName)) id.append(val); + String daoColumnName = getExtractionMapping(sfdcFieldName, false); + if (daoColumnName == null) { + this.map.put(sfdcFieldName, sfdcFieldName); + daoColumnName = sfdcFieldName; + logger.info("SoQL query returned a field that cannot be mapped: " + sfdcFieldName); } + resultRow.put(daoColumnName, val); } mapConstants(resultRow); return resultRow; } + + + protected void mapConstants(Row row) { + for (String constKey : getConstantsMap().keySet()) { + row.put(constKey, getConstantsMap().get(constKey)); + } + } + + public boolean parseSoql(String soql) throws InvalidMappingException { + try { + new SOQLInfo(soql); + } catch (SOQLParserException e) { + throw new InvalidMappingException(e.getMessage(), e); + } + return true; + } public void initSoqlMapping(String soql) { - if (this.soqlInfo == null) try { + if (this.isInitialized) { + return; + } + try { this.soqlInfo = new SOQLInfo(soql); } catch (SOQLParserException e) { throw new InvalidMappingException(e.getMessage(), e); } - if (hasMappings()) return; - // if we didn't map any fields from the properties file, then we do the default soql mapping + initializeSoQLMap(); + + // overlay mapping file for (SOQLFieldInfo fieldInfo : soqlInfo.getSelectedFields()) { - addSoqlFieldMapping(fieldInfo.toString(), fieldInfo); + addSoqlFieldMapping(fieldInfo, fieldInfo.toString()); } + _mapDaoColumns(); + this.isInitialized = true; + } + + public void initSoqlMappingFromResultFields(List resultFields) { + if (this.isInitialized) { + return; + } + initializeSoQLMap(); + for (String resultField : resultFields) { + if (!this.extractionMap.containsKey(resultField)) { + this.extractionMap.put(resultField, resultField); + } + } + _mapDaoColumns(); + this.isInitialized = true; + } + private void _mapDaoColumns() { if (hasDaoColumns()) { - // if no mapping file was provided, but dao col names are known (eg database writer), then we should get - // the dao names correct - List> entries = new LinkedList>(getMap().entrySet()); - clearMap(); + List> soqlBasedMappingEntries = new ArrayList>(getMap().entrySet()); + + clearMappings(); + // FIXME UGLY, NESTED LOOPS - List daoColumns = new LinkedList(getDaoColumns()); - ListIterator daoIter = daoColumns.listIterator(); - while (daoIter.hasNext()) { - String daoColName = daoIter.next(); - ListIterator> entryIter = entries.listIterator(); - while (entryIter.hasNext()) { - Entry ent = entryIter.next(); - String sfdcName = ent.getKey(); - String autoDaoName = ent.getValue(); - if (sfdcName.equalsIgnoreCase(daoColName) || autoDaoName.equalsIgnoreCase(daoColName)) { - putMapping(sfdcName, daoColName); - entryIter.remove(); - daoIter.remove(); + List daoColumns = new ArrayList(getDaoColumns()); + ListIterator daoColumnIter = daoColumns.listIterator(); + while (daoColumnIter.hasNext()) { + String daoColName = daoColumnIter.next(); + ListIterator> oldMappingEntryIter = soqlBasedMappingEntries.listIterator(); + while (oldMappingEntryIter.hasNext()) { + Entry oldMappingEntry = oldMappingEntryIter.next(); + String sfdcFieldName = oldMappingEntry.getKey(); + String oldDaoName = oldMappingEntry.getValue(); + if (sfdcFieldName.equalsIgnoreCase(daoColName) || oldDaoName.equalsIgnoreCase(daoColName)) { + putMapping(sfdcFieldName, daoColName); + oldMappingEntryIter.remove(); + daoColumnIter.remove(); break; } } } - if (!daoColumns.isEmpty()) - throw new InvalidMappingException("The following dao columns could not be mapped: " + daoColumns); - if (!entries.isEmpty()) { - logger.warn("The following select fields were not mapped: " + entries); + copyDaoMappingToExtractionMapping(); + + if (!daoColumns.isEmpty()) { + logger.warn("The following DAO columns could not be mapped: " + daoColumns); + } + if (!soqlBasedMappingEntries.isEmpty()) { + logger.warn("The following SoQL SELECT or result fields were not mapped: " + soqlBasedMappingEntries); + for (SOQLFieldInfo fieldInfo : soqlInfo.getSelectedFields()) { + addSoqlFieldMapping(fieldInfo, fieldInfo.toString()); + } } } } - private void addSoqlFieldMapping(String daoName, SOQLFieldInfo fieldInfo) { - putMapping(normalizeSoql(fieldInfo), daoName); + public void copyDaoMappingToExtractionMapping() { + this.extractionMap.putAll(this.map); + } + + public Collection getDestColumns() { + return this.extractionMap.values(); + } + + public void clearMappings() { + if (this.extractionMap != null) { + this.extractionMap.clear(); + } + this.isInitialized = false; + } + + public void removeMapping(String srcName) { + super.removeMapping(srcName); + this.extractionMap.remove(srcName); + } + + protected Map getMap() { + return Collections.unmodifiableMap(this.extractionMap); } - private String normalizeSoql(SOQLFieldInfo fieldInfo) { + + private void initializeSoQLMap() { + if (this.extractionMap != null) { + this.extractionMap.clear(); + } + + //add extraction mapping + this.extractionMap.putAll(this.map); + } + + private void addExtractionMapping(String daoName, SOQLFieldInfo fieldInfo) { + putMapping(normalizeFieldInSoql(fieldInfo), daoName); + } + + private void addSoqlFieldMapping(SOQLFieldInfo fieldInfo, String soqlFieldName) { + String sfdcFieldName = normalizeFieldInSoql(fieldInfo); + if (!this.extractionMap.containsKey(sfdcFieldName)) { + this.extractionMap.put(sfdcFieldName, soqlFieldName); + } + } + + private String normalizeFieldInSoql(SOQLFieldInfo fieldInfo) { String normalizedFieldName = evalSfdcField(fieldInfo.getFieldName()); return fieldInfo.isAggregate() ? fieldInfo.getAlias() : normalizedFieldName; } @@ -198,6 +347,9 @@ private String evalSfdcField(String fieldExpr) { } private String evalSfdcField(DescribeSObjectResult describeResult, String fieldExpr) { + if (describeResult == null) { + throw new InvalidMappingException("Failed to get entity fields from server"); + } final int splitIdx = fieldExpr.indexOf('.'); if (splitIdx >= 0) { final Field field = getReferenceField(describeResult, fieldExpr.substring(0, splitIdx)); @@ -218,16 +370,16 @@ private String evalSfdcField(DescribeSObjectResult describeResult, String fieldE throw new InvalidMappingException("Connection error while parsing field expression " + fieldExpr, e); } } else { - return getSfdcField(describeResult, fieldExpr).getName(); + return getSfdcFieldName(describeResult, fieldExpr); } } - private Field getSfdcField(DescribeSObjectResult describeResult, String fieldName) { + private String getSfdcFieldName(DescribeSObjectResult describeResult, String fieldName) { for (Field f : describeResult.getFields()) { - if (f.getName().equalsIgnoreCase(fieldName)) return f; + if (f.getName().equalsIgnoreCase(fieldName)) return f.getName(); } - throw new InvalidMappingException("No such field " + fieldName + " on entity " + describeResult.getName()); + return fieldName; } private Field getReferenceField(DescribeSObjectResult describeResult, String relName) { diff --git a/src/main/java/com/salesforce/dataloader/model/LoginCriteria.java b/src/main/java/com/salesforce/dataloader/model/LoginCriteria.java index 8b928f3ea..7846e3760 100644 --- a/src/main/java/com/salesforce/dataloader/model/LoginCriteria.java +++ b/src/main/java/com/salesforce/dataloader/model/LoginCriteria.java @@ -26,16 +26,16 @@ package com.salesforce.dataloader.model; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; /** * all possible criteria for login into the api */ public class LoginCriteria { - public static final int Default = 0; - public static final int Standard = 1; - public static final int Advanced = 2; + public static final int OAuthLogin = 0; + public static final int UsernamePasswordLogin = 1; + public static final int SessionIdLogin = 2; String instanceUrl; String userName; @@ -93,27 +93,20 @@ public void setSessionId(String sessionId) { this.sessionId = sessionId; } - public void updateConfig(Config config) { - config.setValue(Config.USERNAME, config.STRING_DEFAULT); - config.setValue(Config.PASSWORD, config.STRING_DEFAULT); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); - config.setValue(Config.SFDC_INTERNAL_SESSION_ID, config.STRING_DEFAULT); - config.setValue(Config.OAUTH_ENVIRONMENT, config.STRING_DEFAULT); - + public void updateConfig(AppConfig appConfig) { switch (getMode()){ - case LoginCriteria.Standard: - config.setValue(Config.USERNAME, getUserName().trim()); - config.setValue(Config.PASSWORD, getPassword().trim()); - config.setValue(Config.ENDPOINT, getInstanceUrl().trim()); + case LoginCriteria.UsernamePasswordLogin: + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); + appConfig.setValue(AppConfig.PROP_USERNAME, getUserName().trim()); + appConfig.setValue(AppConfig.PROP_PASSWORD, getPassword().trim()); break; - case LoginCriteria.Advanced: - config.setValue(Config.USERNAME, getUserName().trim()); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); - config.setValue(Config.SFDC_INTERNAL_SESSION_ID, getSessionId().trim()); - config.setValue(Config.ENDPOINT, getInstanceUrl().trim()); + case LoginCriteria.SessionIdLogin: + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); + appConfig.setValue(AppConfig.PROP_USERNAME, getUserName().trim()); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID, getSessionId().trim()); break; - case LoginCriteria.Default: - config.setOAuthEnvironment(getEnvironment()); + case LoginCriteria.OAuthLogin: + appConfig.setServerEnvironment(getEnvironment()); break; } } diff --git a/src/main/java/com/salesforce/dataloader/model/NACalendarValue.java b/src/main/java/com/salesforce/dataloader/model/NACalendarValue.java index 21e90a374..86ed50998 100644 --- a/src/main/java/com/salesforce/dataloader/model/NACalendarValue.java +++ b/src/main/java/com/salesforce/dataloader/model/NACalendarValue.java @@ -30,6 +30,7 @@ /** * Represents a null value for a datetime field. */ +@SuppressWarnings("serial") public class NACalendarValue extends GregorianCalendar { private static final NACalendarValue INSTANCE = new NACalendarValue(); diff --git a/src/main/java/com/salesforce/dataloader/model/NADateOnlyCalendarValue.java b/src/main/java/com/salesforce/dataloader/model/NADateOnlyCalendarValue.java new file mode 100644 index 000000000..8e457cf0a --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/model/NADateOnlyCalendarValue.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.model; + +import com.salesforce.dataloader.util.DateOnlyCalendar; + +@SuppressWarnings("serial") +public class NADateOnlyCalendarValue extends DateOnlyCalendar { + + private static final NADateOnlyCalendarValue INSTANCE = new NADateOnlyCalendarValue(); + private static final String NA_VALUE = "#N/A"; + + private NADateOnlyCalendarValue() { + super(); + } + + public static NADateOnlyCalendarValue getInstance() { + return INSTANCE; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + + return NA_VALUE.equals(obj.toString()); + } + + @Override + public int hashCode() { + return NA_VALUE.hashCode(); + } + + @Override + public String toString() { + return NA_VALUE; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/model/Row.java b/src/main/java/com/salesforce/dataloader/model/Row.java index b6ee56dd6..79b9c884a 100644 --- a/src/main/java/com/salesforce/dataloader/model/Row.java +++ b/src/main/java/com/salesforce/dataloader/model/Row.java @@ -25,9 +25,10 @@ */ package com.salesforce.dataloader.model; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -41,30 +42,12 @@ * methods and probably stop implementing Map interface. All Row behavior should be moved into this * class and not be spread in multiple class. */ -public class Row implements Map { - - private static final int DEFAULT_COLUMN_COUNT = 16; // same as HashMap +public class Row implements Map, RowInterface { private final Map internalMap; + private final Map keyMap = new HashMap(); public Row() { - this(DEFAULT_COLUMN_COUNT); - } - - public Row(int columnCount) { - internalMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - } - - public Row(Map internalMap) { - this(internalMap.size()); - this.internalMap.putAll(internalMap); - } - - public static Row emptyRow() { - return new Row(Collections.emptyMap()); - } - - public static Row singleEntryImmutableRow(String key, Object value) { - return new Row(Collections.singletonMap(key, value)); + this.internalMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); } @Override @@ -79,7 +62,11 @@ public boolean isEmpty() { @Override public boolean containsKey(Object key) { - return internalMap.containsKey(key); + String realKey = this.keyMap.get(((String)key).toLowerCase()); + if (realKey == null) { + return false; + } + return internalMap.containsKey(realKey); } @Override @@ -89,26 +76,36 @@ public boolean containsValue(Object value) { @Override public Object get(Object key) { - return internalMap.get(key); + String realKey = this.keyMap.get(((String)key).toLowerCase()); + if (realKey == null) { + return null; + } + return internalMap.get(realKey); } @Override public Object put(String key, Object value) { + this.keyMap.put(key.toLowerCase(), key); return internalMap.put(key, value); } @Override public Object remove(Object key) { + this.keyMap.remove(((String)key).toLowerCase()); return internalMap.remove(key); } @Override public void putAll(Map m) { + for (String key : m.keySet()) { + this.keyMap.put(key.toLowerCase(), key); + } internalMap.putAll(m); } @Override public void clear() { + this.keyMap.clear(); internalMap.clear(); } @@ -134,4 +131,18 @@ public String toString() { " columns=" + internalMap + '}'; } + + public TableRow convertToTableRow(TableHeader header) { + TableRow trow = new TableRow(header); + for (String headerColName : header.getColumns()) { + trow.put(headerColName, this.get(headerColName)); + } + return trow; + } + + @Override + public List getColumnNames() { + Set fieldNameSet = this.keySet(); + return new ArrayList(fieldNameSet); + } } diff --git a/src/main/java/com/salesforce/dataloader/model/RowInterface.java b/src/main/java/com/salesforce/dataloader/model/RowInterface.java new file mode 100644 index 000000000..05f66d9d4 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/model/RowInterface.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.model; + +import java.util.List; + +public interface RowInterface { + public Object put(String key, Object value); + public Object get(Object key); + public List getColumnNames(); +} diff --git a/src/main/java/com/salesforce/dataloader/model/TableHeader.java b/src/main/java/com/salesforce/dataloader/model/TableHeader.java new file mode 100644 index 000000000..a0e1fb7e4 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/model/TableHeader.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.model; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TableHeader { + private final Map columnPositionMap = new HashMap(); + private int lastColPosition = 0; + private List columns; + public TableHeader(List cols) { + this.columns = new ArrayList(cols); + for (String colName : cols) { + if (colName == null) { + continue; + } + columnPositionMap.put(colName.toLowerCase(), lastColPosition); + lastColPosition++; + } + } + + public Integer getColumnPosition(String columnName) { + return columnPositionMap.get(columnName.toLowerCase()); + } + + public List getColumns() { + return new ArrayList(columns); + } + + public void addColumn(String colName) { + this.columns.add(colName); + } + + public void removeColumn(String colName) { + this.columns.remove(colName); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/model/TableRow.java b/src/main/java/com/salesforce/dataloader/model/TableRow.java new file mode 100644 index 000000000..8ab131655 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/model/TableRow.java @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.model; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class TableRow implements Map, RowInterface { + private TableHeader header; + private Object[] cellValues; + + public TableRow(TableHeader header) { + this.header = header; + cellValues = new Object[header.getColumns().size()]; + } + + public TableRow(TableRow rowToCopy) { + this.header = rowToCopy.getHeader(); + cellValues = Arrays.copyOf(rowToCopy.cellValues, rowToCopy.cellValues.length); + } + + public Object get(Object key) { + Integer colPos = this.header.getColumnPosition((String)key); + if (colPos == null) { + return null; + } + return cellValues[colPos]; + } + + public Object put(String key, Object value) { + Integer colPos = this.header.getColumnPosition(key); + if (colPos == null) { + return null; + } + return this.cellValues[colPos] = value; + } + + public TableHeader getHeader() { + return this.header; + } + + public static TableRow emptyRow() { + return new TableRow(new TableHeader(new ArrayList())); + } + + public static TableRow singleEntryImmutableRow(String key, Object value) { + ArrayList headers = new ArrayList(); + headers.add(key); + TableHeader tableHeader = new TableHeader(headers); + TableRow row = new TableRow(tableHeader); + row.put(key, value); + return row; + } + + public int getNonEmptyCellsCount() { + int numNonEmptyCells = 0; + for (int i = 0; i < this.cellValues.length; i++) { + if (cellValues[i] != null) { + numNonEmptyCells++; + } + } + return numNonEmptyCells; + } + + @Override + public List getColumnNames() { + return new ArrayList(this.header.getColumns()); + } + + @Override + public int size() { + return this.header.getColumns().size(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(Object key) { + return this.header.getColumns().contains(key); + } + + @Override + public boolean containsValue(Object value) { + for (Object val : this.cellValues) { + if (val != null && val.equals(value)) { + return true; + } + } + return false; + } + + @Override + public Object remove(Object key) { + Integer colPos = this.header.getColumnPosition((String)key); + if (colPos == null) { + return null; + } + Object value = this.get(key); + this.header.removeColumn((String)key); + return value; + } + + @Override + public void putAll(Map m) { + // TODO Auto-generated method stub + } + + @Override + public void clear() { + ArrayList headerNames = new ArrayList(); + this.header = new TableHeader(headerNames); + } + + @Override + public Set keySet() { + return new HashSet(this.header.getColumns()); + } + + @Override + public Collection values() { + return Arrays.asList(this.cellValues); + } + + @Override + public Set> entrySet() { + HashSet> rowEntrySet = new HashSet>(); + for (String colName : this.header.getColumns()) { + Map.Entry colEntry = new AbstractMap.SimpleEntry(colName, get(colName)); + rowEntrySet.add(colEntry); + } + return rowEntrySet; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowUtil.java b/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowUtil.java new file mode 100644 index 000000000..efc5bb016 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowUtil.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.oauth; + +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import org.apache.http.client.utils.URIBuilder; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.model.OAuthToken; + +/* + * Utility class that handles communication with OAuth service. + * It decouples OAuth UI related classes from OAuth protocol handling. + */ +public class OAuthFlowUtil { + public static String getStartUrlImpl(AppConfig appConfig) throws UnsupportedEncodingException { + return appConfig.getAuthEndpointForCurrentEnv() + + "/services/oauth2/authorize" + + "?response_type=token" + + "&display=popup" + + "&" + appConfig.getClientIdNameValuePair() + + "&redirect_uri=" + + URLEncoder.encode(appConfig.getOAuthRedirectURIForCurrentEnv(), StandardCharsets.UTF_8.name()); + } + + + public static Map getQueryParameters(String url) throws URISyntaxException { + url = url.replace("#","?"); + Map params = new HashMap<>(); + new URIBuilder(url).getQueryParams().stream().forEach(kvp -> params.put(kvp.getName(), kvp.getValue())); + return params; + } + + public static boolean handleCompletedUrl(String url, AppConfig appConfig) throws URISyntaxException { + Map params = getQueryParameters(url); + + if (params.containsKey("access_token")){ + //we don't use most of this but I still like to track what we should get + OAuthToken token = new OAuthToken(); + token.setInstanceUrl(params.get("instance_url")); + token.setId(params.get("id")); + token.setAccessToken(params.get("access_token")); + + //optional parameters + if (params.containsKey("refresh_token")) { + token.setRefreshToken(params.get("refresh_token")); + } + + //currently unused parameters + if (params.containsKey("scope")) { + token.setScope(params.get("scope")); + } + if (params.containsKey("signature")) { + token.setSignature(params.get("signature")); + } + if (params.containsKey("token_type")) { + token.setTokenType(params.get("token_type")); + } + if (params.containsKey("issued_at")) { + String issued_at = params.get("issued_at"); + if (issued_at != null && !issued_at.equals("")) { + token.setIssuedAt(Long.valueOf(issued_at)); + } + } + + appConfig.setAuthEndpointForCurrentEnv(token.getInstanceUrl()); + appConfig.setValue(AppConfig.PROP_OAUTH_ACCESSTOKEN, token.getAccessToken()); + appConfig.setValue(AppConfig.PROP_OAUTH_REFRESHTOKEN, token.getRefreshToken()); + appConfig.setValue(AppConfig.PROP_OAUTH_INSTANCE_URL, token.getInstanceUrl()); + return true; + } + + return false; + } +} diff --git a/src/main/java/com/salesforce/dataloader/oauth/OAuthSecretFlowUtil.java b/src/main/java/com/salesforce/dataloader/oauth/OAuthSecretFlowUtil.java new file mode 100644 index 000000000..30675f5e3 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/oauth/OAuthSecretFlowUtil.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.oauth; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +import org.apache.http.message.BasicNameValuePair; + +import com.salesforce.dataloader.client.SimplePost; +import com.salesforce.dataloader.client.SimplePostFactory; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.util.OAuthBrowserLoginRunner; + +/* + * Utility class that handles communication with OAuth service. + * It decouples OAuth UI related classes from OAuth protocol handling. + */ +public class OAuthSecretFlowUtil { + public static String getStartUrlImpl(AppConfig appConfig) throws UnsupportedEncodingException { + return appConfig.getAuthEndpointForCurrentEnv() + + "/services/oauth2/authorize" + + "?response_type=code" + + "&display=popup" + + "&" + appConfig.getClientIdNameValuePair() + + "&redirect_uri=" + + URLEncoder.encode(appConfig.getOAuthRedirectURIForCurrentEnv(), StandardCharsets.UTF_8.name()); + } + + public static SimplePost handleSecondPost(String code, AppConfig appConfig) throws IOException, ParameterLoadException { + String server = appConfig.getAuthEndpointForCurrentEnv() + "/services/oauth2/token"; + SimplePost client = SimplePostFactory.getInstance(appConfig, server, + new BasicNameValuePair("grant_type", "authorization_code"), + new BasicNameValuePair("code", code), + new BasicNameValuePair(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()), + new BasicNameValuePair("client_secret", appConfig.getOAuthClientSecretForCurrentEnv()), + new BasicNameValuePair("redirect_uri", appConfig.getOAuthRedirectURIForCurrentEnv()) + ); + client.post(); + if (client.isSuccessful()) { + OAuthBrowserLoginRunner.processSuccessfulLogin(client.getInput(), appConfig); + } + return client; + } + + public static String handleInitialUrl(String url) throws URISyntaxException { + Map queryParameters = OAuthFlowUtil.getQueryParameters(url); + return queryParameters.get("code"); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/process/DataLoaderRunner.java b/src/main/java/com/salesforce/dataloader/process/DataLoaderRunner.java index de5414f1a..b0adfb307 100644 --- a/src/main/java/com/salesforce/dataloader/process/DataLoaderRunner.java +++ b/src/main/java/com/salesforce/dataloader/process/DataLoaderRunner.java @@ -29,22 +29,320 @@ * @author Lexi Viripaeff * @input DataLoaderRunner -------------- @ * ---------------- */ + +import com.salesforce.dataloader.install.Installer; +import com.salesforce.dataloader.security.EncryptionUtil; +import com.salesforce.dataloader.ui.UIUtils; +import com.salesforce.dataloader.util.AppUtil; +import com.salesforce.dataloader.util.ExitException; + +import java.io.File; +import java.io.FileFilter; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import java.lang.management.ManagementFactory; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.util.ArrayList; +import java.util.Map; + +import javax.xml.parsers.FactoryConfigurationError; + +import org.apache.commons.io.filefilter.WildcardFileFilter; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.exception.ControllerInitializationException; -import com.salesforce.dataloader.ui.UIUtils; -public class DataLoaderRunner { +public class DataLoaderRunner extends Thread { + private static final String LOCAL_SWT_DIR = "./target/"; + private static final String PATH_SEPARATOR = System.getProperty("path.separator"); + private static final String FILE_SEPARATOR = System.getProperty("file.separator"); + private static Logger logger = DLLogManager.getLogger(DataLoaderRunner.class); + private static int exitCode = AppUtil.EXIT_CODE_NO_ERRORS; + + public void run() { + // called just before the program closes + HttpClientTransport.closeHttpClient(); + } - public static void main(String[] args) { - Controller controller; + public static void main(String[] commandLineOptions) { + try { + Map argsMap = AppUtil.convertCommandArgsArrayToArgMap(commandLineOptions); + if (!argsMap.containsKey(AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH)) { + AppUtil.showBanner(); + } + runApp(commandLineOptions, null); + System.exit(exitCode); + } catch (ExitException ex) { + System.exit(ex.getExitCode()); + } finally { + if (logger != null) { + logger.debug("Number of server API invocations = " + HttpClientTransport.getServerInvocationCount()); + } + } + System.exit(exitCode); + } + + public static IProcess runApp(String[] commandLineOptions, ILoaderProgress monitor) { + Controller controller = null; + Runtime.getRuntime().addShutdownHook(new DataLoaderRunner()); try { - controller = Controller.getInstance("ui", false); - controller.createAndShowGUI(); - } catch (ControllerInitializationException e) { - UIUtils.errorMessageBox(new Shell(new Display()), e); + controller = Controller.getInstance(AppUtil.convertCommandArgsArrayToArgMap(commandLineOptions)); + } catch (FactoryConfigurationError | Exception ex) { + logger.fatal(ex); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); + } + if (AppUtil.getAppRunMode() == AppUtil.APP_RUN_MODE.BATCH) { + return ProcessRunner.runBatchMode(AppUtil.convertCommandArgsArrayToArgMap(commandLineOptions), monitor); + } else if (AppUtil.getAppRunMode() == AppUtil.APP_RUN_MODE.ENCRYPT) { + EncryptionUtil.main(commandLineOptions); + } else { + Map argsMap = AppUtil.convertCommandArgsArrayToArgMap(commandLineOptions); + /* Run in the UI mode, get the controller instance with batchMode == false */ + Installer.install(argsMap); + if (argsMap.containsKey(AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH) + && "true".equalsIgnoreCase(argsMap.get(AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH))){ + try { + String defaultBrowser = System.getProperty("org.eclipse.swt.browser.DefaultType"); + if (defaultBrowser == null) { + logger.debug("org.eclipse.swt.browser.DefaultType not set for UI mode on Windows"); + } else { + logger.debug("org.eclipse.swt.browser.DefaultType set to " + defaultBrowser + " for UI mode on Windows"); + } + controller.createAndShowGUI(); + } catch (Exception e) { + UIUtils.errorMessageBox(new Shell(new Display()), e); + } + } else { // SWT_NATIVE_LIB_IN_JAVA_LIB_PATH not set + rerunWithSWTNativeLib(commandLineOptions); + } + } + return null; + } + + public static void setExitCode(int codeVal) { + exitCode = codeVal; + } + + private static void rerunWithSWTNativeLib(String[] args) { + String javaExecutablePath = null; + try { + javaExecutablePath = ProcessHandle.current() + .info() + .command() + .orElseThrow(); + } catch (Exception e) { + // fail silently + } + if (javaExecutablePath == null) { + javaExecutablePath = System.getProperty("java.home") + + FILE_SEPARATOR + "bin" + FILE_SEPARATOR + "java"; + } + // java command is the first argument + ArrayList jvmArgs = new ArrayList(128); + logger.debug("java executable path: " + javaExecutablePath); + jvmArgs.add(javaExecutablePath); + + // JVM options + // set -XstartOnFirstThread for MacOS + String osName = System.getProperty("os.name").toLowerCase(); + if ((osName.contains("mac")) || (osName.startsWith("darwin"))) { + jvmArgs.add("-XstartOnFirstThread"); + logger.debug("added JVM arg -XstartOnFirstThread"); + } + + // set JVM arguments + // add JVM arguments specified in the command line + jvmArgs.addAll(ManagementFactory.getRuntimeMXBean().getInputArguments()); + File swtJarFileHandle = getSWTJarFileHandle(); + if (swtJarFileHandle == null) { + logger.error("Unable to find SWT jar for " + + System.getProperty("os.name") + " : " + + System.getProperty("os.arch")); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); + } + + // set classpath + String classpath = System.getProperty("java.class.path"); + String SWTJarPath = swtJarFileHandle.getAbsolutePath(); + if (classpath != null && !classpath.isBlank()) { + classpath = SWTJarPath + PATH_SEPARATOR + classpath; + } else { + classpath = SWTJarPath; + } + jvmArgs.add("-cp"); + jvmArgs.add(classpath); + logger.debug("set java.class.path=" + classpath); + + // specify name of the class with main method + jvmArgs.add(DataLoaderRunner.class.getName()); + logger.debug("added class to execute - " + DataLoaderRunner.class.getName()); + + // specify application arguments + logger.debug("added following arguments:"); + for (int i = 0; i < args.length; i++) { + jvmArgs.add(args[i]); + logger.debug(" " + args[i]); + } + + // add the argument to indicate that JAVA_LIB_PATH has the folder containing SWT native libraries + jvmArgs.add(AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH + "=true"); + logger.debug(" " + AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH + "=true"); + + // set System proxy info as proxy server defaults + String proxyHost = null; + int proxyPort = 0; + Proxy systemProxy = AppUtil.getSystemHttpsProxy(args); + if (systemProxy != null) { + InetSocketAddress addr = (InetSocketAddress) systemProxy.address(); + + if (addr != null) { + proxyHost = addr.getHostName(); + proxyPort = addr.getPort(); + jvmArgs.add(AppConfig.CLI_OPTION_SYSTEM_PROXY_HOST + "=" + proxyHost); + jvmArgs.add(AppConfig.CLI_OPTION_SYSTEM_PROXY_PORT + "=" + proxyPort); + } + } + AppUtil.exec(jvmArgs, null); + } + + private static String constructSwtJarNameFromOSAndArch(boolean skipOSAndArch) { + String swtJarPrefix = "swt"; + String swtJarSuffix = "*.jar"; + + String osNameStr = getOSName(); + String archStr = System.getProperty("os.arch"); + + if (archStr.toLowerCase().contains("amd")) { + archStr = "x86_64"; + } + + // e.g. swtwin32_x86_64*.jar or swtmac_aarch64*.jar + String pathStr = swtJarPrefix + + (skipOSAndArch ? "" : osNameStr + "_" + archStr) + + swtJarSuffix; + + return pathStr; + } + + private static String getOSName() { + String osNameProperty = System.getProperty("os.name"); + + if (osNameProperty == null) { + throw new RuntimeException("os.name property is not set"); + } + else { + osNameProperty = osNameProperty.toLowerCase(); + } + + if (osNameProperty.contains("win")) { + return "win32"; + } else if (osNameProperty.contains("mac")) { + return "mac"; + } else if (osNameProperty.contains("linux") || osNameProperty.contains("nix")) { + return "linux"; + } else { + throw new RuntimeException("Unknown OS name: " + osNameProperty); + } + } + + private static File getSWTJarFileHandleFromWildcard(String parentDirStr, String childFileStr) { + if (parentDirStr == null || parentDirStr.isBlank() || childFileStr == null || childFileStr.isBlank()) { + return null; + } + if (parentDirStr.contains("*")) { + // path to the file has a wildcard. Assume that it is present only at the parent folder level + String[] subpaths = parentDirStr.split("\\*"); + File grandparentDir = new File(subpaths[0]); + File[] possibleParentDirs = grandparentDir.listFiles(); + for (File possibleParentDir : possibleParentDirs) { + if (possibleParentDir.isDirectory()) { + File possibleSWTJarFile = getSWTJarFileHandleFromWildcard( + possibleParentDir.getAbsolutePath(), childFileStr); + if (possibleSWTJarFile != null) { + return possibleSWTJarFile; + } + } + } + } + File parentDir = new File(parentDirStr); + if (!parentDir.exists()) { + return null; + } + FileFilter fileFilter = WildcardFileFilter.builder().setWildcards(childFileStr).get(); + File[] files = parentDir.listFiles(fileFilter); + if (files != null && files.length > 0) { + return files[0]; + } + return null; + } + + private static File getSWTJarFileHandle() { + String[]parentDirOfSWTDirArray = + {AppUtil.getDirContainingClassJar(DataLoaderRunner.class) + , "." + , ".." + , LOCAL_SWT_DIR + }; + Boolean[] skipOSAndArchInFileNameConstructionArray = {Boolean.FALSE, Boolean.TRUE}; + for (String parentDirOfSwtDir : parentDirOfSWTDirArray) { + if (parentDirOfSwtDir == null) { + continue; + } + for (Boolean skipOSAndArchVal : skipOSAndArchInFileNameConstructionArray) { + File swtJarFileHandle = getSWTJarFileHandleFromDir(parentDirOfSwtDir, skipOSAndArchVal); + if (swtJarFileHandle != null) { + return swtJarFileHandle; + } + // look into sub-directories + swtJarFileHandle = getSWTJarFileHandleFromDir(parentDirOfSwtDir + "/*", skipOSAndArchVal); + if (swtJarFileHandle != null) { + return swtJarFileHandle; + } + } + } + + // try to get it from the CLASSPATH + MapenvVars = System.getenv(); + String classPathStr = envVars.get("CLASSPATH"); + File swtJarFileHandle = getSWTJarFileFromClassPath(classPathStr); + if (swtJarFileHandle != null) { + return swtJarFileHandle; + } + classPathStr = System.getProperty("java.class.path"); + return getSWTJarFileFromClassPath(classPathStr); + } + + private static File getSWTJarFileHandleFromDir(String dir, boolean skipOSAndArchVal) { + String swtJarPathWithParentDirWildcard = constructSwtJarNameFromOSAndArch(skipOSAndArchVal); + File swtJarFileHandle = getSWTJarFileHandleFromWildcard(dir, swtJarPathWithParentDirWildcard); + if (swtJarFileHandle != null) { + logger.debug("Found SWT jar at " + swtJarFileHandle.getAbsolutePath()); + } + return swtJarFileHandle; + } + + private static File getSWTJarFileFromClassPath(String classPathStr) { + if (classPathStr == null) { + return null; + } + logger.debug("CLASSPATH = " + classPathStr); + if (classPathStr.toLowerCase().contains("swt")) { + String[] pathValues = classPathStr.split(PATH_SEPARATOR); + for (String pathVal : pathValues) { + if (pathVal.toLowerCase().contains("swt")) { + File swtFile = new File(pathVal); + if (swtFile.exists()) { + return swtFile; + } + } + } } + return null; } } diff --git a/src/main/java/com/salesforce/dataloader/process/FindJavaRunner.java b/src/main/java/com/salesforce/dataloader/process/FindJavaRunner.java deleted file mode 100644 index 0f553c8dc..000000000 --- a/src/main/java/com/salesforce/dataloader/process/FindJavaRunner.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.process; - -/* - * A small utility to find java home when the user has not set it - */ -public class FindJavaRunner { - public static void main(String[] args) { - String home = System.getProperty("java.home"); - if (home != null){ - System.out.println(home); - } - } -} diff --git a/src/main/java/com/salesforce/dataloader/process/IProcess.java b/src/main/java/com/salesforce/dataloader/process/IProcess.java new file mode 100644 index 000000000..3dadfe7be --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/process/IProcess.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.process; + +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.controller.Controller; + +public interface IProcess { + public Controller getController(); + public ILoaderProgress getMonitor(); +} diff --git a/src/main/java/com/salesforce/dataloader/process/ProcessConfig.java b/src/main/java/com/salesforce/dataloader/process/ProcessConfig.java index ba70e0a4f..58ff22fb2 100644 --- a/src/main/java/com/salesforce/dataloader/process/ProcessConfig.java +++ b/src/main/java/com/salesforce/dataloader/process/ProcessConfig.java @@ -54,11 +54,12 @@ import java.io.File; -import org.apache.log4j.Logger; - +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.ProcessInitializationException; + import org.springframework.context.ApplicationContext; import org.springframework.context.support.FileSystemXmlApplicationContext; @@ -70,7 +71,7 @@ */ public class ProcessConfig { - private static Logger logger = Logger.getLogger(ProcessConfig.class); + private static Logger logger = DLLogManager.getLogger(ProcessConfig.class); public final static String DEFAULT_CONFIG_FILENAME = "process-conf.xml"; private final static String DEFAULT_SCHEDULER_FACTORY_NAME = "schedulerFactory"; @@ -80,7 +81,7 @@ public ProcessConfig() { } private static String getConfigFileLocation() { - File confFile = new File(Controller.getConfigDir(), DEFAULT_CONFIG_FILENAME); + File confFile = new File(AppConfig.getConfigurationsDir(), DEFAULT_CONFIG_FILENAME); return confFile.getAbsolutePath(); } diff --git a/src/main/java/com/salesforce/dataloader/process/ProcessRunner.java b/src/main/java/com/salesforce/dataloader/process/ProcessRunner.java index ed668cc7d..f356d9f57 100644 --- a/src/main/java/com/salesforce/dataloader/process/ProcessRunner.java +++ b/src/main/java/com/salesforce/dataloader/process/ProcessRunner.java @@ -55,111 +55,127 @@ * @author Lexi Viripaeff */ -import java.util.*; -import java.util.Calendar; - -import javax.xml.parsers.FactoryConfigurationError; - -import org.apache.log4j.Logger; -import org.quartz.*; -import org.springframework.beans.factory.InitializingBean; - import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.progress.NihilistProgressAdapter; -import com.salesforce.dataloader.config.*; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.LastRunProperties; +import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.exception.*; +import com.salesforce.dataloader.exception.ControllerInitializationException; +import com.salesforce.dataloader.exception.OAuthBrowserLoginRunnerException; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.exception.ProcessInitializationException; +import com.salesforce.dataloader.ui.Labels; +import com.salesforce.dataloader.util.AppUtil; +import com.salesforce.dataloader.util.ExitException; +import com.salesforce.dataloader.util.OAuthBrowserLoginRunner; import com.sforce.soap.partner.fault.ApiFault; -public class ProcessRunner implements InitializingBean, Job, Runnable { +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import org.springframework.beans.factory.InitializingBean; + +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +public class ProcessRunner implements InitializingBean, IProcess { /** - * Comment for PROCESS_NAME + * Comment for DYNABEAN_ID */ - public static final String PROCESS_NAME = "process.name"; //logger - private static final Logger logger = Logger.getLogger(ProcessRunner.class); - - // Name of the current engine runner. Improves readability of the log output - String name; + private static Logger logger = DLLogManager.getLogger(ProcessRunner.class); + + private String name = null; // name of the loaded process DynaBean // config override parameters private final Map configOverrideMap = new HashMap(); private Controller controller; - + + private ILoaderProgress monitor; + + private static final String PROP_NAME_ARRAY[] = { + AppConfig.PROP_OPERATION, + AppConfig.PROP_USERNAME, + AppConfig.PROP_PASSWORD, + AppConfig.PROP_DAO_TYPE, + AppConfig.PROP_DAO_NAME, + AppConfig.PROP_ENTITY, + }; + /** * Enforce use of factory method - getInstance() by hiding the constructor */ protected ProcessRunner() { } - @Override - public synchronized void run() { - run(NihilistProgressAdapter.get()); - } - - public synchronized void run(ILoaderProgress monitor) { + public synchronized void run(ILoaderProgress monitor) throws Exception { + if (monitor == null) { + monitor = new NihilistProgressAdapter(); + } + this.monitor = monitor; final String oldName = Thread.currentThread().getName(); - final String name = getName(); + String name = getName(); - setThreadName(name); + if (name != null && !name.isBlank()) { + setThreadName(name); + } try { - controller = Controller.getInstance(name, true); + controller = Controller.getInstance(getConfigOverrideMap()); } catch (ControllerInitializationException e) { throw new RuntimeException(e); } try { logger.info(Messages.getString("Process.initializingEngine")); //$NON-NLS-1$ - Config config = controller.getConfig(); - // load parameter overrides (from command line or caller context) - logger.info(Messages.getString("Process.loadingParameters")); //$NON-NLS-1$ - config.loadParameterOverrides(getConfigOverrideMap()); + AppConfig appConfig = controller.getAppConfig(); + if (!(appConfig.contains(AppConfig.PROP_USERNAME) && appConfig.contains(AppConfig.PROP_PASSWORD)) + && appConfig.getBoolean(AppConfig.PROP_OAUTH_LOGIN_FROM_BROWSER)) { + doLoginFromBrowser(appConfig); + } + // Make sure that the required properties are specified. + validateConfigProperties(appConfig); + if (name == null || name.isBlank()) { + // this can occur only if "process.name" is not specified as a command line option + name = appConfig.getString(AppConfig.PROP_OPERATION); + this.setName(name); + setThreadName(name); + }; // create files for status output unless it's an extract and status output is disabled - if (!config.getOperationInfo().isExtraction() || config.getBoolean(Config.ENABLE_EXTRACT_STATUS_OUTPUT)) { - controller.setStatusFiles(config.getString(Config.OUTPUT_STATUS_DIR), true, false); + if (!appConfig.getOperationInfo().isExtraction() || appConfig.getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT)) { + controller.setStatusFiles(appConfig.getString(AppConfig.PROP_OUTPUT_STATUS_DIR), true, false); } - logger.info(Messages.getFormattedString("Process.loggingIn", config.getString(Config.ENDPOINT))); //$NON-NLS-1$ + logger.info(Messages.getFormattedString("Process.loggingIn", appConfig.getAuthEndpointForCurrentEnv())); //$NON-NLS-1$ if (controller.login()) { - // instantiate the data access object - controller.createDao(); - - logger.info(Messages.getString("Process.checkingDao")); //$NON-NLS-1$ - // check to see if the the data access object has any connection problems - controller.getDao().checkConnection(); - // get the field info (using the describe call) logger.info(Messages.getString("Process.settingFieldTypes")); //$NON-NLS-1$ controller.setFieldTypes(); - // get the object reference info (using the describe call) - logger.info(Messages.getString("Process.settingReferenceTypes")); //$NON-NLS-1$ - controller.setReferenceDescribes(); - // instantiate the map logger.info(Messages.getString("Process.creatingMap")); //$NON-NLS-1$ - controller.createMapper(); + controller.initializeOperation(appConfig.getString(AppConfig.PROP_DAO_TYPE), + appConfig.getString(AppConfig.PROP_DAO_NAME), appConfig.getString(AppConfig.PROP_ENTITY)); // execute the requested operation controller.executeAction(monitor); // save last successful run date // FIXME look into a better place so that long runs don't skew this - config.setValue(LastRun.LAST_RUN_DATE, Calendar.getInstance().getTime()); - config.saveLastRun(); + appConfig.setValue(LastRunProperties.LAST_RUN_DATE, Calendar.getInstance().getTime()); + appConfig.saveLastRun(); } else { logger.fatal(Messages.getString("Process.loginError")); //$NON-NLS-1$ } } catch (ApiFault e) { // this is necessary, because the ConnectionException doesn't display the login fault message throw new RuntimeException(e.getExceptionMessage(), e); - } catch (Exception e) { - throw new RuntimeException(e); } finally { // make sure all is closed and saved if (controller.getDao() != null) controller.getDao().close(); @@ -168,9 +184,9 @@ public synchronized void run(ILoaderProgress monitor) { setThreadName(oldName); } } - - private static void ensureLogging() throws FactoryConfigurationError { - Controller.initLog(); + + public ILoaderProgress getMonitor() { + return this.monitor; } private void setThreadName(final String name) { @@ -189,11 +205,15 @@ public synchronized Map getConfigOverrideMap() { } public synchronized void setConfigOverrideMap(Map configOverrideMap) { - if (this.configOverrideMap.isEmpty()) + if (this.configOverrideMap.isEmpty()) { this.configOverrideMap.putAll(configOverrideMap); - else + if (getName() != null && !getName().isBlank()) { + this.configOverrideMap.put(AppConfig.PROP_PROCESS_NAME, getName()); + } + } else { throw new IllegalStateException("Attempting to set configOverrideMap but there are already " + this.configOverrideMap.size() + " entries"); + } } public synchronized String getName() { @@ -216,44 +236,50 @@ public void afterPropertiesSet() throws Exception { } } - private static boolean validateCmdLineArgs (String[] args) { - - for (int i = 0; i < args.length; i++) { - String arg = args[i]; - if ("-help".equals(arg) ) { - System.out.println(Messages.getString("Process.help1")); - System.out.println(Messages.getString("Process.help2")); - System.out.println(Messages.getString("Process.help3")); - System.out.println(Messages.getString("Process.help4")); - System.out.println(Messages.getString("Process.help5")); - System.out.println(Messages.getString("Process.help6")); - return false; - } + public static void logErrorAndExitProcess(String message, Throwable throwable, int exitCode) { + if (throwable == null) { + logger.fatal(message); + } else { // throwable != null + logger.fatal(message, throwable); } - return true; - } - - private static void topLevelError(String message, Throwable err) { - ensureLogging(); - logger.fatal(message, err); - System.exit(-1); + throw new ExitException(throwable, exitCode); } - - public static void main(String[] args) { + + public static ProcessRunner runBatchMode(MapcommandLineOptionsMap, ILoaderProgress progressMonitor) throws UnsupportedOperationException { ProcessRunner runner = null; try { // create the process - runner = ProcessRunner.getInstance(args); - if (runner == null) topLevelError("Process runner is null", new NullPointerException()); - } catch (Throwable t) { - topLevelError("Failed to create process", t); - } - try { + runner = ProcessRunner.getInstance(commandLineOptionsMap); + if (runner == null) { + logErrorAndExitProcess("Process runner is null", + new NullPointerException(), AppUtil.EXIT_CODE_CLIENT_ERROR); + } // run the process - runner.run(); - } catch (Throwable e) { - topLevelError("Unable to run process " + runner.getName(), e); + runner.run(progressMonitor); + progressMonitor = runner.getMonitor(); + if (progressMonitor != null) { + if (progressMonitor.isCanceled()) { + logErrorAndExitProcess(progressMonitor.getMessage(), null, AppUtil.EXIT_CODE_CLIENT_ERROR); + } else if (!progressMonitor.isSuccess()) { + logErrorAndExitProcess(progressMonitor.getMessage(), null, AppUtil.EXIT_CODE_SERVER_ERROR); + } else if (AppConfig.getCurrentConfig() != null + && AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_PROCESS_EXIT_WITH_ERROR_ON_FAILED_ROWS_BATCH_MODE) + && progressMonitor.getNumberRowsWithError() > 0) { + DataLoaderRunner.setExitCode(AppUtil.EXIT_CODE_RESULTS_ERROR); + } + } + } catch (Throwable t) { + if (t.getClass().equals(UnsupportedOperationException.class)) { + // this is done to allow integration tests to continue + // after a negative test of an operation results in an exception + throw (UnsupportedOperationException)t; + } + if (t.getClass().equals(ExitException.class)) { + throw (ExitException)t; + } + logErrorAndExitProcess("Unable to run process", t, AppUtil.EXIT_CODE_OPERATION_ERROR); } + return runner; } /** @@ -262,50 +288,40 @@ public static void main(String[] args) { * @param args String set of name=value pairs of arguments for the runner * @throws ProcessInitializationException */ - private static ProcessRunner getInstance(String[] args) throws ProcessInitializationException { - ensureLogging(); - - if(!validateCmdLineArgs(args)) { - return null; - } - - Map argMap = getArgMap(args); - ProcessRunner runner = getInstance(argMap); - return runner; - } /** - * @param argMap + * @param commandLineOptionsMap * @return instance of ProcessRunner * @throws ProcessInitializationException */ - public static ProcessRunner getInstance(Map argMap) throws ProcessInitializationException { + private static synchronized ProcessRunner getInstance(Map commandLineOptionsMap) throws ProcessInitializationException { + logger.info(Messages.getString("Process.initializingEngine")); //$NON-NLS-1$ + String dynaBeanID = commandLineOptionsMap.get(AppConfig.PROP_PROCESS_NAME); ProcessRunner runner; - if(argMap != null && argMap.containsKey(PROCESS_NAME)) { - // if process name is specified, get it from configuration - String processName = argMap.get(PROCESS_NAME); - runner = ProcessConfig.getProcessInstance(processName); - // command line param values override the ones in config file - runner.getConfigOverrideMap().putAll(argMap); - } else { - // if process.name is not given, load defaults & override with command-line args + if (dynaBeanID == null || dynaBeanID.isEmpty()) { + // operation and other process params are specified in config.properties + logger.info(AppConfig.PROP_PROCESS_NAME + + "is not specified in the command line. Loading the process properties from config.properties."); runner = new ProcessRunner(); - runner.setConfigOverrideMap(argMap); - } - return runner; - } - - private static Map getArgMap(String[] args) { - //every arg is a name=value config setting, save it in a map of name/value pairs - Map argMap = new HashMap(); - for (int i = 0; i < args.length; i++) { - String[] argArray = args[i].split("="); //$NON-NLS-1$ - - if (argArray.length == 2) { - argMap.put(argArray[0], argArray[1]); + + if (commandLineOptionsMap.containsKey(AppConfig.PROP_PROCESS_THREAD_NAME)) { + runner.setName(commandLineOptionsMap.get(AppConfig.PROP_PROCESS_THREAD_NAME)); } + } else { + // process name specified in the command line arg. + // Load its DynaBean through process-conf.xml + logger.info(AppConfig.PROP_PROCESS_NAME + + "is specified in the command line. Loading DynaBean with id " + + dynaBeanID + + " from process-conf.xml located in folder " + + AppConfig.getConfigurationsDir()); + runner = ProcessConfig.getProcessInstance(dynaBeanID); } - return argMap; + + // Override properties set in config.properties and process-conf.xml + // by properties specified as command line options + runner.getConfigOverrideMap().putAll(commandLineOptionsMap); + return runner; } /** @@ -318,15 +334,47 @@ public static ProcessRunner getInstance(String processName) throws ProcessInitia return ProcessConfig.getProcessInstance(processName); } - /* (non-Javadoc) - * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) - */ - @Override - public void execute(JobExecutionContext arg0) throws JobExecutionException { - run(); - } - public Controller getController() { return controller; } + + private static void validateConfigProperties(AppConfig appConfig) throws ProcessInitializationException { + if (appConfig == null) { + throw new ProcessInitializationException("Configuration not initialized"); + } + + for (String propName : PROP_NAME_ARRAY) { + String propVal = appConfig.getString(propName); + if (propName.equals(AppConfig.PROP_PASSWORD) && (propVal == null || propVal.isBlank())) { + // OAuth access token must be specified if password is not specified + propVal = appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN); + } + if (propVal == null || propVal.isBlank()) { + logger.fatal(Messages.getFormattedString("Config.errorNoRequiredParameter", propName)); + throw new ParameterLoadException(Messages.getFormattedString("Config.errorNoRequiredParameter", propName)); + } + } + } + + private void doLoginFromBrowser(AppConfig appConfig) throws OAuthBrowserLoginRunnerException { + final String verificationURLStr; + final OAuthBrowserLoginRunner loginRunner; + try { + loginRunner = new OAuthBrowserLoginRunner(appConfig, true); + verificationURLStr = loginRunner.getVerificationURLStr(); + System.out.println(Labels.getString("OAuthInBrowser.batchModeMessage1")); + System.out.println(Labels.getString("OAuthInBrowser.batchModeURL") + verificationURLStr); + System.out.println(Labels.getFormattedString("OAuthInBrowser.batchModeMessage2", loginRunner.getUserCode())); + } catch (Exception ex) { + logger.error(ex.getMessage()); + throw new OAuthBrowserLoginRunnerException(ex.getMessage()); + } + while (!loginRunner.isLoginProcessCompleted()) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + // fail silently + } + } + } } diff --git a/src/main/java/com/salesforce/dataloader/process/ProcessScheduler.java b/src/main/java/com/salesforce/dataloader/process/ProcessScheduler.java deleted file mode 100644 index 4c9fa94dc..000000000 --- a/src/main/java/com/salesforce/dataloader/process/ProcessScheduler.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.process; -/* - * Copyright (c) 2005, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -import java.util.Timer; - -import org.apache.log4j.Logger; - -import com.salesforce.dataloader.controller.Controller; - -/** - * Abstract class that should be extended to manage - * Processes for a specific application that needs - * sheduled processes. - */ -public class ProcessScheduler extends Timer { - - /** Instance of this class so it can be called from the command line */ - protected static ProcessScheduler processScheduler; - - /** Log4J */ - private static Logger logger = Logger.getLogger(ProcessScheduler.class); - - public ProcessScheduler() { - Controller.initLog(); - } - - /** - * The startProcess method will obtain a list of process groups from the config file. - * Each process group is implemented in as a Timer. The call to the ProcessRunner.init() - * method returns after the the process group has been scheduled. - */ - public void startProcesses() { - // try using scheduler factory bean - try { - // this will ensure that the scheduler starts - ProcessConfig.getSchedulerInstance(); - } catch (Exception e) { - logger.fatal(e.getMessage(), e); - return; - } - - } - - /** - * The ProcessManager should be the main point of execution from the command line. - * @param args - */ - public static void main(String[] args) { - processScheduler = new ProcessScheduler(); - processScheduler.startProcesses(); - } -} diff --git a/src/main/java/com/salesforce/dataloader/security/EncryptionAesUtil.java b/src/main/java/com/salesforce/dataloader/security/EncryptionAesUtil.java new file mode 100644 index 000000000..714c9c70e --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/security/EncryptionAesUtil.java @@ -0,0 +1,243 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.security; + +import com.salesforce.dataloader.util.AppUtil; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +/** + * com.salesforce.dataloader.security + * + * @author xbian + */ +public class EncryptionAesUtil { + + private static final Logger LOGGER = DLLogManager.getLogger(EncryptionAesUtil.class); + + + private static AppUtil.OSType detectedOS; + + // Support single text encryption and decryption + + private static Cipher cipher; + private byte[] cipherKey = null; + + // 16 bytes was used for 128 bit AES encryption + public static final int ENCRYPTION_KEY_LENGTH_IN_BYTES = 16; + public static final int IV_LENGTH_IN_BYTES = 16; + + public static final String DEFAULT_KEYFILE_NAME = "dataLoader.key"; + + static { + try { + cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + detectedOS = AppUtil.getOSType(); + + } catch (Exception e) { + LOGGER.error("Fail to initialize encryption: " + e.getMessage()); + throw new RuntimeException("Fail to initialize encryption: ", e); + } + } + + + private byte[] extractIvBytes(byte[] cipheredText) { + byte[] iv = new byte[IV_LENGTH_IN_BYTES]; + System.arraycopy(cipheredText, 0, iv, 0, IV_LENGTH_IN_BYTES); + return iv; + } + + private byte[] extractCipherContent(byte[] cipheredText) { + byte[] cipheredPassword = new byte[cipheredText.length - IV_LENGTH_IN_BYTES]; + System.arraycopy(cipheredText, IV_LENGTH_IN_BYTES, cipheredPassword, 0, cipheredText.length - IV_LENGTH_IN_BYTES); + return cipheredPassword; + + } + + private byte[] concatenateByteArray(byte[] a, byte[] b) { + + byte[] c = new byte[a.length + b.length]; + System.arraycopy(a, 0, c, 0, a.length); + System.arraycopy(b, 0, c, a.length, b.length); + return c; + } + + public byte[] generateEncryptionKey() { + // The secure random uses threadId and tick to make sure it is not repeatable + // If a call to setSeed had not occurred previously, + // the first call to this method forces this SecureRandom object to seed itself. + // This self-seeding will not occur if setSeed was previously called. + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[ENCRYPTION_KEY_LENGTH_IN_BYTES]; + random.nextBytes(bytes); + return bytes; + } + + public byte[] generateIv() { + SecureRandom random = new SecureRandom(); + byte bytes[] = new byte[IV_LENGTH_IN_BYTES]; + random.nextBytes(bytes); + return bytes; + } + + public String createUserProfileKeyName() { + String path = Paths.get(System.getProperty("user.home"), ".dataloader").toString(); + File customDir = new File(path); + + if (customDir.exists()) { + LOGGER.debug(customDir + " exists"); + } else if (customDir.mkdirs()) { + if (detectedOS == AppUtil.OSType.MACOSX || detectedOS == AppUtil.OSType.LINUX) { + // set all reading to false + customDir.setReadable(false, false); + // only owner can read + customDir.setReadable(true, true); + customDir.setExecutable(false, false); + customDir.setExecutable(true, true); + } + LOGGER.info(customDir + " was created"); + + } else { + LOGGER.error(customDir + " was not created"); + throw new RuntimeException("Cannot create folder:" + path); + } + return Paths.get(path, DEFAULT_KEYFILE_NAME).toString(); + } + + // If given path not existing, create one. + public String createKeyFileIfNotExisting(String filePath) throws GeneralSecurityException { + if (filePath == null || filePath.isEmpty() || !Files.exists(Paths.get(filePath))) { + // no valid file path provided, check if default one exists + if (filePath == null || filePath.isEmpty()) { + filePath = createUserProfileKeyName(); + } + if (!Files.exists(Paths.get(filePath))) { + byte[] key = generateEncryptionKey(); + try (FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(key); + } catch (IOException io) { + throw new GeneralSecurityException("Failed to open file:" + filePath, io); + } + // Windows platform is already readable only to owner. + if (detectedOS == AppUtil.OSType.MACOSX || detectedOS == AppUtil.OSType.LINUX) { + File file = new File(filePath); + // set all reading to false + file.setReadable(false, false); + // set Owner reading to true + file.setReadable(true, true); + } + } + } + setCipherKeyFromFilePath(filePath); + return filePath; + } + + public void resetCipherKey() { + cipherKey = null; + } + + public void setCipherKeyFromFilePath(String filePath) throws GeneralSecurityException { + byte[] data = new byte[1024]; + try (FileInputStream fis = new FileInputStream(filePath)) { + int size = fis.read(data); + if (size < ENCRYPTION_KEY_LENGTH_IN_BYTES) + throw new GeneralSecurityException("Keyfile content is too short:" + filePath); + else { + cipherKey = new byte[ENCRYPTION_KEY_LENGTH_IN_BYTES]; + System.arraycopy(data, 0, cipherKey, 0, ENCRYPTION_KEY_LENGTH_IN_BYTES); + } + } catch (IOException io) { + throw new GeneralSecurityException("Failed to open file: " + filePath, io); + } + } + + private void ensureKeyIsSet() throws GeneralSecurityException { + if (cipherKey != null) { + return; + } + // if no key was set up already, create one + createKeyFileIfNotExisting(null); + + } + + public String encryptMsg(String msg) throws GeneralSecurityException { + try { + ensureKeyIsSet(); + return EncryptionUtil.bytesToText(encryptMsg(msg, cipherKey)); + } catch (Exception e) { + LOGGER.error("Fail to encrypt message: " + e.getMessage()); + throw new GeneralSecurityException("Error to encrypt message: ", e); + } + } + + public String decryptMsg(String cipherMsgString) throws GeneralSecurityException { + try { + ensureKeyIsSet(); + byte[] cipherMsg = EncryptionUtil.textToBytes(cipherMsgString); + return decryptMsg(cipherMsg, cipherKey); + } catch (Exception e) { + LOGGER.error("Fail to decrypt message: " + e.getMessage()); + throw new GeneralSecurityException("Error to decrypt message: ", e); + } + } + + public byte[] encryptMsg(String msg, byte[] encryptionKey) throws GeneralSecurityException { + + if (encryptionKey == null || encryptionKey.length != ENCRYPTION_KEY_LENGTH_IN_BYTES) + throw new GeneralSecurityException("Encryption key is null or has invalid length"); + SecretKeySpec key = new SecretKeySpec(encryptionKey, "AES"); + byte[] ivBytes = generateIv(); + IvParameterSpec iv = new IvParameterSpec(ivBytes); + cipher.init(Cipher.ENCRYPT_MODE, key, iv); + byte[] cipherText = cipher.doFinal(msg.getBytes()); + return concatenateByteArray(ivBytes, cipherText); + } + + public String decryptMsg(byte[] cipherMsg, byte[] encryptionKey) throws GeneralSecurityException { + if (encryptionKey == null || encryptionKey.length != ENCRYPTION_KEY_LENGTH_IN_BYTES) + throw new GeneralSecurityException("Encryption key is null or has invalid length"); + SecretKeySpec key = new SecretKeySpec(encryptionKey, "AES"); + byte[] iv = extractIvBytes(cipherMsg); + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); + byte[] cipherContent = extractCipherContent(cipherMsg); + return new String(cipher.doFinal(cipherContent)); + } +} diff --git a/src/main/java/com/salesforce/dataloader/security/EncryptionUtil.java b/src/main/java/com/salesforce/dataloader/security/EncryptionUtil.java index 355294ffb..9a1af8e0e 100644 --- a/src/main/java/com/salesforce/dataloader/security/EncryptionUtil.java +++ b/src/main/java/com/salesforce/dataloader/security/EncryptionUtil.java @@ -25,47 +25,18 @@ */ package com.salesforce.dataloader.security; -import org.apache.log4j.Logger; +import java.security.GeneralSecurityException; -import java.io.*; -import java.security.*; -import java.security.spec.InvalidKeySpecException; - -import javax.crypto.*; -import javax.crypto.spec.DESKeySpec; +import com.salesforce.dataloader.util.AppUtil; public class EncryptionUtil { - private static final Logger LOGGER = Logger.getLogger(EncryptionUtil.class); - private Key gKey = null; - private String gCipherSeed = "namastearrigato"; - private String gCipherKey = "51dda30be226233d"; - - public EncryptionUtil() { - } - - /** - * - */ - synchronized public void resetCryptoKey() { - gKey = null; - gCipherKey = null; - } - - /** - * @param sKey - */ - synchronized public void setCipherKey(String sKey) { - gCipherKey = sKey; - } - /** * Convert text to bytes * - * @param text * @return bytes for the input text */ - private static byte[] textToBytes(String text) { + public static byte[] textToBytes(String text) { byte[] baBytes = new byte[text.length() / 2]; for (int j = 0; j < text.length() / 2; j++) { Integer tmpInteger = Integer.decode(new String("0x" + text.substring(j * 2, (j * 2) + 2))); @@ -74,7 +45,7 @@ private static byte[] textToBytes(String text) { { tmpValue = (tmpValue - 127) * -1; } - tmpInteger = new Integer(tmpValue); + tmpInteger = Integer.valueOf(tmpValue); baBytes[j] = tmpInteger.byteValue(); } @@ -84,10 +55,9 @@ private static byte[] textToBytes(String text) { /** * Convert bytes to text * - * @param bytes * @return text for the input bytes */ - private static String bytesToText(byte[] bytes) { + public static String bytesToText(byte[] bytes) { StringBuffer sb = new StringBuffer(bytes.length * 2); for (int i = 0; i < bytes.length; i++) { int num = bytes[i]; @@ -98,181 +68,18 @@ private static String bytesToText(byte[] bytes) { } sb.append(hex); } - return sb.toString(); } - /** - * Create a key for encryption and decryption - * - * @return Key - * @throws NoSuchAlgorithmException - * @throws InvalidKeySpecException - * @throws InvalidKeyException - */ - synchronized private Key getCryptoKey() throws NoSuchAlgorithmException, InvalidKeySpecException, - InvalidKeyException { - if (gKey != null) return gKey; - - String sKeyText = gCipherKey != null ? gCipherKey : generateKey(gCipherSeed); - byte[] baKeyBytes = textToBytes(sKeyText); - - try { - SecretKeyFactory desFactory = SecretKeyFactory.getInstance("DES"); - gKey = desFactory.generateSecret(new DESKeySpec(baKeyBytes)); - } catch (InvalidKeyException e) { - throw e; - } catch (InvalidKeySpecException e) { - throw e; - } - - return gKey; - } - - /** - * Create the cipher object for encryption or decryption. We create a DES cipher with PKCS#5 padding - * - * @return Cipher - * @throws NoSuchAlgorithmException - * @throws NoSuchPaddingException - */ - private static Cipher createCipher() throws NoSuchAlgorithmException, NoSuchPaddingException { - try { - return Cipher.getInstance("DES/ECB/PKCS5Padding"); - } catch (NoSuchAlgorithmException e) { - throw e; - } catch (NoSuchPaddingException e) { - throw e; - } - } - - /** - * Encrypt a string using whatever is the default encryption technique for the system. - * - * @param clearText - * @return The encrypted string - * @throws GeneralSecurityException - */ - public String encryptString(String clearText) throws GeneralSecurityException { - if (clearText == null) return clearText; - Key oKey = getCryptoKey(); - Cipher oCipher = createCipher(); - byte[] encryptedBytes; - - if (oCipher == null) { return clearText; } - - byte[] inputBytes = clearText.getBytes(); - - try { - oCipher.init(Cipher.ENCRYPT_MODE, oKey); - } catch (InvalidKeyException e) { - throw e; - } - - try { - encryptedBytes = oCipher.doFinal(inputBytes); - } catch (IllegalBlockSizeException e) { - throw e; - } catch (BadPaddingException e) { - throw e; - } - - return bytesToText(encryptedBytes); - } - - /** - * Decrypt a string using whatever is the default encryption technique for the system. - * - * @param cipherText - * @return The decrypted string - * @throws NoSuchPaddingException - * @throws NoSuchAlgorithmException - * @throws BadPaddingException - * @throws IllegalBlockSizeException - * @throws InvalidKeyException - */ - public String decryptString(String cipherText) throws GeneralSecurityException { - if (cipherText == null) return cipherText; - Key key = getCryptoKey(); - Cipher cipher = createCipher(); - - if (cipher == null) { return cipherText; } - - try { - cipher.init(Cipher.DECRYPT_MODE, key); - } catch (InvalidKeyException e) { - throw e; - } - - byte[] baCipherBytes = textToBytes(cipherText); - - try { - return new String(cipher.doFinal(baCipherBytes)); - } catch (BadPaddingException e) { - throw e; - } catch (IllegalBlockSizeException e) { - throw e; - } - } - - /** - * Create a DES key and return a string version of the raw bits. - * - * @param seed - * @return A key for encryption - * @throws NoSuchAlgorithmException - */ - public static String generateKey(String seed) throws NoSuchAlgorithmException { - - byte[] seedBytes = seed.getBytes(); - SecureRandom random = new SecureRandom(seedBytes); - KeyGenerator keygen = null; - try { - keygen = KeyGenerator.getInstance("DES"); - } catch (NoSuchAlgorithmException e) { - throw e; - } - - keygen.init(random); - Key key = keygen.generateKey(); - - byte[] keyBytes = key.getEncoded(); - - return bytesToText(keyBytes); - } - - /** - * @param keyFilename - * @throws IOException - */ - public void setCipherKeyFromFilePath(String keyFilename) throws IOException { - try { - File keyFile = new File(keyFilename); - if (keyFile.exists() && keyFile.canRead()) { - FileReader fr = new FileReader(keyFile); - BufferedReader br = new BufferedReader(fr); - String line = null; - while ((line = br.readLine()) != null) { - break; - } - if (line != null) { - resetCryptoKey(); // Reset Key - setCipherKey(line); - } - } else { - throw new IOException("Cannot Access Key File: " + keyFilename); - } - } catch (IOException e) { - throw e; - } - } private static void printUsage() { - LOGGER.info("\nUtility to encrypt a string based on a static or a provided key"); - LOGGER.info("Options (mutually exclusive - use one at a time): \n" - + "\t-g Generate key based on seed\n" - + "\t-v [Path to Key] Validate whether decryption of encrypted value matches the decrypted value, optionally provide key file\n" - + "\t-e [Path to Key] Encrypt a plain text value, optionally provide key file (generate key using option -g)"); + String usage = "\nUtility to encrypt a string based on a static or a provided key\n" + + "Options (mutually exclusive - use one at a time): \n" + + "\t-e [keyfile] Encrypt a plain text value using optional keyfile\n" + + "\t-d [keyfile] Decrypt an encrypted text back to plain text value using optional keyfile\n" + + "\t-k [keyfile] Generate keyfile with optional path to keyfile\n" + + "\n keyfile defaults to $HOME(%userprofile% on Windows)/.dataloader/dataLoader.key if not specified"; + System.out.println(usage); } public static void main(String[] args) { @@ -280,105 +87,100 @@ public static void main(String[] args) { // args[1] = key (optional) if (args.length < 1) { printUsage(); - System.exit(-1); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } int i = 0; - String option = args[i]; - if (option.length() < 2 || option.charAt(0) != '-') { - LOGGER.info("Invalid option format: " + args[i]); - System.exit(-1); + String operation = ""; + for (String arg : args) { + if (arg.startsWith("-")) { + operation = arg; + break; + } + i++; + } + if (operation.length() < 2 || operation.charAt(0) != '-') { + System.out.println("Invalid option format: " + args[i]); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } // make sure enough arguments are provided - if (arrayTooSmall(args, i)) { - LOGGER.info("Option '" + option + "' requires at least one parameter. Please check usage.\n"); + if (arrayTooSmall(args, i) && operation.charAt(1) != 'k') { + System.out.println("Option '" + operation + "' requires at least one parameter. Please check usage.\n"); printUsage(); - System.exit(-1); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } // advance index to param and save the param value - String param = args[++i]; - switch (option.charAt(1)) { - case 'g': - try { - String key = generateKey(param); - LOGGER.info(key); - } catch (NoSuchAlgorithmException e) { - LOGGER.error("Error generating key: " + e.getMessage()); - System.exit(-1); - } - break; - case 'v': - // verify if encrypted value matches the encrypted the encrypted - // cleartext - // if optional key file is provided, use it - if (arrayTooSmall(args, i)) { - LOGGER.info("Please provide decrypted value to validate against"); - printUsage(); - System.exit(-1); - } - String decryptExpected = args[++i]; - EncryptionUtil dec = new EncryptionUtil(); + String param = null; + switch (operation.charAt(1)) { + case 'e': + EncryptionAesUtil enc = new EncryptionAesUtil(); + param = args[++i]; if (!arrayTooSmall(args, i)) { + String keyFilename = args[++i]; try { - dec.setCipherKeyFromFilePath(args[++i]); - } catch (IOException e) { - LOGGER.error("Error setting the key from file: " - + args[i] + ", error: " + e.getMessage()); - System.exit(-1); + enc.setCipherKeyFromFilePath(keyFilename); + } catch (Exception e) { + System.out.println("Error setting the key from file: " + + keyFilename + ", error: " + e.getMessage()); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } } try { - String decrypted = dec.decryptString(param); - LOGGER.info("Decryption of encrypted value " - + (decryptExpected.equals(decrypted) ? "MATCHES" - : "DOES NOT MATCH") + " the expected value"); - } catch (GeneralSecurityException e) { - LOGGER.error("Error decrypting string: " + param - + ", error: " + e.getMessage()); - System.exit(-1); + String encrypted = enc.encryptMsg(param); + System.out.println("The output string of encryption is: \n" + encrypted); + } catch (Exception e) { + System.out.println("Error setting the key: " + e.getMessage()); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } + break; - case 'e': - // if optional key file is provided, use it - EncryptionUtil enc = new EncryptionUtil(); - if (!arrayTooSmall(args, i)) { - String keyFilename = args[++i]; - File keyFile = new File(keyFilename); - if (!keyFile.exists() && !keyFile.canRead()) { - LOGGER.warn("Please ensure that the key file '" - + keyFilename + "' exists and is readable"); + + case 'k': + // optional [Path to key file] + try { + EncryptionAesUtil encAes = new EncryptionAesUtil(); + if (i == args.length - 2 || i == args.length - 1) { + String filePath = encAes.createKeyFileIfNotExisting(i == args.length - 1 ? null : args[i + 1]); + System.out.println("Keyfile \"" + filePath + "\" was created! "); + } else { + System.out.println("Please provide correct parameters!"); printUsage(); - System.exit(-1); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } + } catch (Exception e) { + System.out.println("Error occurred: " + e.getMessage()); + } + break; + + case 'd': + EncryptionAesUtil encAes = new EncryptionAesUtil(); + String encryptMsg = args[++i]; + if (!arrayTooSmall(args, i)) { + String keyFilename = args[++i]; try { - enc.setCipherKeyFromFilePath(keyFilename); - } catch (IOException e) { - LOGGER.error("Error setting the key from file: " - + keyFilename + ", error: " + e.getMessage()); - System.exit(-1); + encAes.setCipherKeyFromFilePath(keyFilename); + } catch (GeneralSecurityException e) { + System.out.println("Failed in decryption: " + e.getMessage() + "\n Make sure using the same keyfile to decrypt."); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } } try { - // encrypt the given string and output to STDOUT - String encrypted; - encrypted = enc.encryptString(param); - LOGGER.info(encrypted); - } catch (GeneralSecurityException e) { - LOGGER.error("Error encrypting string: " + param - + ", error: " + e.getMessage()); - System.exit(-1); + String plainText = encAes.decryptMsg(encryptMsg); + System.out.println("The output string of decryption is: \n" + plainText); + } catch (Exception e) { + System.out.println("Failed in decryption: " + e.getMessage() + "\n Make sure using the same keyfile to decrypt."); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } break; + default: - LOGGER.error("Unsupported option: " + option); + System.out.println("Unsupported option: " + operation); printUsage(); - System.exit(-1); + System.exit(AppUtil.EXIT_CODE_CLIENT_ERROR); } } /** - * @param array - * @param index * @return true if array is too small to increment the index */ private static boolean arrayTooSmall(String[] array, int index) { diff --git a/src/main/java/com/salesforce/dataloader/ui/AdvancedSettingsDialog.java b/src/main/java/com/salesforce/dataloader/ui/AdvancedSettingsDialog.java index ed64866e2..e6315d53b 100644 --- a/src/main/java/com/salesforce/dataloader/ui/AdvancedSettingsDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/AdvancedSettingsDialog.java @@ -27,36 +27,56 @@ package com.salesforce.dataloader.ui; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.*; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.LastRunProperties; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.util.AppUtil; +import com.salesforce.dataloader.util.LoggingUtil; -import org.apache.log4j.Logger; +import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.*; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.VerifyEvent; +import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; -import org.eclipse.swt.layout.*; -import org.eclipse.swt.widgets.*; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.LastRun; -import com.salesforce.dataloader.controller.Controller; -import com.sforce.soap.partner.Connector; - -public class AdvancedSettingsDialog extends Dialog { - private String message; - private String input; - private Controller controller; - private Text textBatch; - private Text textQueryBatch; - private Text textSplitterValue; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +import java.util.HashMap; +import java.util.Map; +import java.util.TimeZone; + +public class AdvancedSettingsDialog extends BaseDialog { + private Text textImportBatchSize; + private Link labelImportBatchSize; + private Text textExportBatchSize; + private Text textUploadCSVDelimiterValue; + private Text textQueryResultsDelimiterValue; private Button buttonNulls; + private Text labelNulls; private Text textRule; - private Text textEndpoint; + private Text textProdEndpoint; + private Text textSBEndpoint; private Button buttonCompression; - private Button buttonResetUrl; private Text textTimeout; private Text textRowToStart; private Text textProxyHost; @@ -65,453 +85,672 @@ public class AdvancedSettingsDialog extends Dialog { private Text textProxyUsername; private Text textProxyPassword; private Text textTimezone; - - private final String defaultServer; - - private final Logger logger = Logger.getLogger(AdvancedSettingsDialog.class); - private Button buttonHideWelcomeScreen; + private Button buttonLocalSystemTimezone; + private Text textProductionPartnerClientID; + private Text textSandboxPartnerClientID; + private Text textProductionBulkClientID; + private Text textSandboxBulkClientID; + private Text textWizardWidth; + private Text textWizardHeight; + + private Button buttonShowWelcomeScreen; + private Button buttonShowLoaderUpgradeScreen; private Button buttonOutputExtractStatus; + private Button buttonSortExtractFields; + private Button buttonLimitQueryResultColumnsToFieldsInQuery; private Button buttonReadUtf8; private Button buttonWriteUtf8; private Button buttonEuroDates; private Button buttonTruncateFields; - private Button buttonUseBulkApi; + private Text labelTruncateFields; + private Button buttonFormatPhoneFields; + private Button buttonKeepAccountTeam; + private Button buttonUndeleteEnabled; + private Button buttonHardDeleteEnabled; + private Button buttonUpdateWithExternalId; + private Text labelUpdateWithExternalId; + private Button buttonCacheDescribeGlobalResults; + private Button buttonIncludeRTFBinaryDataInQueryResults; + private Button buttonUseSOAPApi; + private Button buttonUseBulkV1Api; + private Button buttonUseBulkV2Api; private Button buttonBulkApiSerialMode; private Button buttonBulkApiZipContent; private Button buttonCsvComma; private Button buttonCsvTab; - private Button buttonCsvOther; + private Button buttonLoginFromBrowser; + private Button buttonCloseWizardOnFinish; + private Button buttonPopulateResultsFolderOnWizardFinishStep; + private static final String[] LOGGING_LEVEL = { "ALL", "DEBUG", "INFO", "WARN", "ERROR", "FATAL" }; + private Combo comboLoggingLevelDropdown; + private Composite soapApiOptionsComposite; + private Composite bulkApiOptionsComposite; + private Composite exportBatchSizeComposite; + private Composite importBatchSizeComposite; + private Composite zipContentComposite; /** * InputDialog constructor * - * @param parent - * the parent + * @param parent the parent */ public AdvancedSettingsDialog(Shell parent, Controller controller) { - super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - setText(Labels.getString("AdvancedSettingsDialog.title")); //$NON-NLS-1$ - setMessage(Labels.getString("AdvancedSettingsDialog.message")); //$NON-NLS-1$ - this.controller = controller; - - URI uri; - String server = ""; - try { - uri = new URI(Connector.END_POINT); - server = uri.getScheme() + "://" + uri.getHost(); //$NON-NLS-1$ - } catch (URISyntaxException e) { - logger.error(e); - } - defaultServer = server; - } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; - } - - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Gets the input - * - * @return String - */ - public String getInput() { - return input; + super(parent, controller); } - /** - * Sets the input - * - * @param input - * the new input - */ - public void setInput(String input) { - this.input = input; + private final Map apiOptionsMap = new HashMap(); + private boolean useBulkAPI = false; + private boolean useBulkV2API = false; + private boolean useSoapAPI = false; + + + private void setEnabled(Label label, boolean isEnabled) { + int color = isEnabled ? SWT.COLOR_BLACK : SWT.COLOR_GRAY; + label.setForeground(getParent().getDisplay().getSystemColor(color)); } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public String open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); + + private void setEnabled(Control ctrl, boolean enabled) { + if (ctrl instanceof Composite) { + Composite comp = (Composite) ctrl; + for (Control child : comp.getChildren()) { + if (enabled && comp == this.soapApiOptionsComposite) { + setEnabled(child, !this.buttonUpdateWithExternalId.getSelection()); + } else { + setEnabled(child, enabled); + } + } + if (enabled && comp == this.soapApiOptionsComposite) { + setEnabled(buttonUpdateWithExternalId, true); + setEnabled(labelUpdateWithExternalId, true); + setEnabled(buttonNulls, true); + setEnabled(labelNulls, true); + setEnabled(buttonTruncateFields, true); + setEnabled(labelTruncateFields, true); } + } else if (ctrl instanceof Label) { + setEnabled((Label)ctrl, enabled); + } else { // Button, Checkbox, Dropdown list etc + ctrl.setEnabled(enabled); } - // Return the entered value, or null - return input; } - - private final Map oldBulkAPIDependencies = new HashMap(); - - private void initBulkApiSetting(boolean enabled) { - setButtonEnabled(Config.BULK_API_SERIAL_MODE, buttonBulkApiSerialMode, enabled); - setButtonEnabled(Config.BULK_API_ZIP_CONTENT, buttonBulkApiZipContent, enabled); - setButtonEnabled(Config.INSERT_NULLS, buttonNulls, !enabled); - setButtonEnabled(Config.TRUNCATE_FIELDS, buttonTruncateFields, !enabled); + + private void setAllApiOptions() { + for (Button apiButton : apiOptionsMap.keySet()) { + enableApiOptions(apiButton, false); + } + Button selectedButton = useBulkAPI ? this.buttonUseBulkV1Api : (useBulkV2API ? this.buttonUseBulkV2Api : this.buttonUseSOAPApi); + enableApiOptions(selectedButton, true); + setEnabled(this.exportBatchSizeComposite, useSoapAPI); + setEnabled(this.importBatchSizeComposite, !useBulkV2API); + setEnabled(this.zipContentComposite, !useBulkV2API); + this.buttonUndeleteEnabled.setSelection(useSoapAPI); + this.buttonHardDeleteEnabled.setSelection(!useSoapAPI); } - - private void setButtonEnabled(String configKey, Button b, boolean enabled) { - Boolean previousValue = oldBulkAPIDependencies.put(b, b.getSelection()); - b.setSelection(enabled ? (previousValue != null ? previousValue : this.controller.getConfig().getBoolean( - configKey)) : false); - b.setEnabled(enabled); + + private void enableApiOptions(Button apiButton, boolean isEnabled) { + Composite apiOptionsComposite = apiOptionsMap.get(apiButton); + if (apiOptionsComposite != null) { + setEnabled(apiOptionsComposite, isEnabled); + } + } + + private void initializeAllApiOptions() { + apiOptionsMap.put(buttonUseSOAPApi, soapApiOptionsComposite); + apiOptionsMap.put(buttonUseBulkV1Api, bulkApiOptionsComposite); + setAllApiOptions(); } /** * Creates the dialog's contents * - * @param shell - * the dialog window + * @param shell the dialog window */ - private void createContents(final Shell shell) { - - final Config config = controller.getConfig(); + protected void createContents(final Shell shell) { + final AppConfig appConfig = getController().getAppConfig(); + GridData data; + + GridLayout layout = new GridLayout(1, false); + layout.verticalSpacing = 10; + shell.setLayout(layout); + data = new GridData(GridData.FILL_BOTH); + shell.setLayoutData(data); // Create the ScrolledComposite to scroll horizontally and vertically ScrolledComposite sc = new ScrolledComposite(shell, SWT.H_SCROLL | SWT.V_SCROLL); - + data = new GridData(GridData.FILL_BOTH); + data.heightHint = 600; + sc.setLayoutData(data); + // Create the parent Composite container for the three child containers Composite container = new Composite(sc, SWT.NONE); GridLayout containerLayout = new GridLayout(1, false); container.setLayout(containerLayout); - shell.setLayout(new FillLayout()); - - GridData data; - data = new GridData(GridData.FILL_HORIZONTAL); - data.heightHint = 50; - data.widthHint = 400; - - // START TOP COMPONENT - - Composite topComp = new Composite(container, SWT.NONE); - GridLayout layout = new GridLayout(1, false); - layout.marginHeight = 0; - layout.marginWidth = 0; - layout.verticalSpacing = 0; - topComp.setLayout(layout); - topComp.setLayoutData(data); - Label blank = new Label(topComp, SWT.NONE); + Composite restComp = new Composite(container, SWT.NONE); + data = new GridData(GridData.FILL_BOTH); + restComp.setLayoutData(data); + layout = new GridLayout(2, false); + layout.verticalSpacing = 10; + restComp.setLayout(layout); + + Label blank = new Label(restComp, SWT.NONE); data = new GridData(GridData.FILL_HORIZONTAL); data.heightHint = 10; blank.setLayoutData(data); blank.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + // Show the message - Label label = new Label(topComp, SWT.NONE); - label.setText(message); + Composite messageComp = new Composite(restComp, SWT.NONE); data = new GridData(GridData.FILL_HORIZONTAL); - data.heightHint = 30; - data.widthHint = 370; - - Font f = label.getFont(); + data.grabExcessHorizontalSpace = true; + data.horizontalSpan = 2; + messageComp.setLayoutData(data); + messageComp.setLayout(new GridLayout(2, false)); + + Link dialogMessage = createLink(messageComp, "message", null, null); + data = new GridData(GridData.FILL_HORIZONTAL); + data.grabExcessHorizontalSpace = true; + Font f = dialogMessage.getFont(); FontData[] farr = f.getFontData(); FontData fd = farr[0]; fd.setStyle(SWT.BOLD); - label.setFont(new Font(Display.getCurrent(), fd)); + dialogMessage.setFont(new Font(Display.getCurrent(), fd)); + dialogMessage.setLayoutData(data); + dialogMessage.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); - label.setLayoutData(data); - label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + Link settingsHelp = new Link(messageComp, SWT.None); + settingsHelp.setText("Help"); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + settingsHelp.setLayoutData(data); + settingsHelp.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent arg0) { + } + @Override + public void mouseDown(MouseEvent arg0) { + SettingsHelpDialog helpDlg = new SettingsHelpDialog(getParent(), getController()); + helpDlg.open(); + } + @Override + public void mouseUp(MouseEvent arg0) { + } + }); - Label labelSeparator = new Label(topComp, SWT.SEPARATOR | SWT.HORIZONTAL); + Label labelSeparator = new Label(restComp, SWT.SEPARATOR | SWT.HORIZONTAL); data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalSpan = 2; + labelSeparator.setBackground(getParent().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); labelSeparator.setLayoutData(data); // END TOP COMPONENT // START MIDDLE COMPONENT - Composite restComp = new Composite(container, SWT.NONE); - data = new GridData(GridData.FILL_BOTH); - restComp.setLayoutData(data); - layout = new GridLayout(2, false); - layout.verticalSpacing = 10; - restComp.setLayout(layout); + // Hide welcome screen + createLink(restComp, null, null, AppConfig.PROP_HIDE_WELCOME_SCREEN); + buttonShowWelcomeScreen = new Button(restComp, SWT.CHECK); + buttonShowWelcomeScreen.setSelection(!appConfig.getBoolean(AppConfig.PROP_HIDE_WELCOME_SCREEN)); - // Hide welecome screen - Label labelHideWelcomeScreen = new Label(restComp, SWT.RIGHT); - labelHideWelcomeScreen.setText(Labels.getString("AdvancedSettingsDialog.hideWelcomeScreen")); //$NON-NLS-1$ - labelHideWelcomeScreen.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); + // Hide welcome screen + createLink(restComp, null, null, AppConfig.PROP_SHOW_LOADER_UPGRADE_SCREEN); + buttonShowLoaderUpgradeScreen = new Button(restComp, SWT.CHECK); + buttonShowLoaderUpgradeScreen.setSelection(appConfig.getBoolean(AppConfig.PROP_SHOW_LOADER_UPGRADE_SCREEN)); - buttonHideWelcomeScreen = new Button(restComp, SWT.CHECK); - buttonHideWelcomeScreen.setSelection(config.getBoolean(Config.HIDE_WELCOME_SCREEN)); + blank = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + data.heightHint = 15; + blank.setLayoutData(data); - //batch size - Label labelBatch = new Label(restComp, SWT.RIGHT); - labelBatch.setText(Labels.getString("AdvancedSettingsDialog.batchSize")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelBatch.setLayoutData(data); + labelSeparator = new Label(restComp, SWT.SEPARATOR | SWT.HORIZONTAL); + data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalIndent = 100; + data.horizontalSpan = 2; + labelSeparator.setLayoutData(data); + + Composite apiChoiceComposite = new Composite(restComp, SWT.None); + layout = new GridLayout(3, true); + layout.verticalSpacing = 10; + apiChoiceComposite.setLayout(layout); + data = new GridData(); + data.horizontalSpan = 2; + data.horizontalAlignment = SWT.FILL; + data.grabExcessHorizontalSpace = true; + apiChoiceComposite.setLayoutData(data); - textBatch = new Text(restComp, SWT.BORDER); - textBatch.setText(config.getString(Config.LOAD_BATCH_SIZE)); - textBatch.setTextLimit(8); - textBatch.addVerifyListener(new VerifyListener() { + // Enable Bulk API Setting + useBulkAPI = appConfig.getBoolean(AppConfig.PROP_BULK_API_ENABLED) && !appConfig.getBoolean(AppConfig.PROP_BULKV2_API_ENABLED); + useBulkV2API = appConfig.getBoolean(AppConfig.PROP_BULKV2_API_ENABLED); + useSoapAPI = !useBulkAPI && !useBulkV2API; + + buttonUseSOAPApi = new Button(apiChoiceComposite, SWT.RADIO); + buttonUseSOAPApi.setToolTipText(Labels.getFormattedString("AdvancedSettingsDialog.uiTooltip.useSOAPApi", + new String[] {AppConfig.PROP_BULK_API_ENABLED, AppConfig.PROP_BULKV2_API_ENABLED})); + buttonUseSOAPApi.setSelection(useSoapAPI); + buttonUseSOAPApi.setText(Labels.getString("AdvancedSettingsDialog.uiLabel.useSOAPApi")); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + data.grabExcessHorizontalSpace = true; + buttonUseSOAPApi.setLayoutData(data); + buttonUseSOAPApi.addSelectionListener(new SelectionAdapter() { @Override - public void verifyText(VerifyEvent event) { - event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + useSoapAPI = buttonUseSOAPApi.getSelection(); + if (!useSoapAPI) { + return; + } + useBulkAPI = false; + useBulkV2API = false; + setAllApiOptions(); + + // update batch size when this setting changes + int newDefaultBatchSize = getController().getAppConfig().getDefaultImportBatchSize(false, false); + logger.debug("Setting batch size to " + newDefaultBatchSize); + textImportBatchSize.setText(String.valueOf(newDefaultBatchSize)); + String[] args = {getImportBatchLimitsURL(), + Integer.toString(appConfig.getMaxImportBatchSize(useBulkAPI || useBulkV2API, useBulkV2API))}; + labelImportBatchSize.setText( + Labels.getFormattedString(AdvancedSettingsDialog.class.getSimpleName() + ".uiLabel." + AppConfig.PROP_IMPORT_BATCH_SIZE, args)); + labelImportBatchSize.redraw(); } }); - data = new GridData(); - data.widthHint = 50; - textBatch.setLayoutData(data); + + buttonUseBulkV1Api = new Button(apiChoiceComposite, SWT.RADIO); + buttonUseBulkV1Api.setToolTipText(Labels.getFormattedString("AdvancedSettingsDialog.uiTooltip.useBulkV1Api", + new String[] {AppConfig.PROP_BULK_API_ENABLED, AppConfig.PROP_BULKV2_API_ENABLED})); + buttonUseBulkV1Api.setSelection(useBulkAPI); + buttonUseBulkV1Api.setText(Labels.getString("AdvancedSettingsDialog.uiLabel.useBulkV1Api")); + data = new GridData(GridData.HORIZONTAL_ALIGN_CENTER); + data.grabExcessHorizontalSpace = true; + buttonUseBulkV1Api.setLayoutData(data); + buttonUseBulkV1Api.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + useBulkAPI = buttonUseBulkV1Api.getSelection(); + if (!useBulkAPI) { + return; + } + useSoapAPI = false; + useBulkV2API = false; + setAllApiOptions(); + + // update batch size when this setting changes + int newDefaultBatchSize = getController().getAppConfig().getDefaultImportBatchSize(true, false); + logger.debug("Setting batch size to " + newDefaultBatchSize); + textImportBatchSize.setText(String.valueOf(newDefaultBatchSize)); + String[] args = {getImportBatchLimitsURL(), + Integer.toString(appConfig.getMaxImportBatchSize(useBulkAPI || useBulkV2API, useBulkV2API))}; + labelImportBatchSize.setText( + Labels.getFormattedString(AdvancedSettingsDialog.class.getSimpleName() + ".uiLabel." + AppConfig.PROP_IMPORT_BATCH_SIZE, args)); + labelImportBatchSize.redraw(); + } + }); + + // Enable Bulk API 2.0 Setting + buttonUseBulkV2Api = new Button(apiChoiceComposite, SWT.RADIO); + buttonUseBulkV2Api.setToolTipText(Labels.getFormattedString("AdvancedSettingsDialog.uiTooltip.useBulkV2Api", + new String[] {AppConfig.PROP_BULK_API_ENABLED, AppConfig.PROP_BULKV2_API_ENABLED})); + buttonUseBulkV2Api.setSelection(useBulkV2API); + buttonUseBulkV2Api.setText(Labels.getString("AdvancedSettingsDialog.uiLabel.useBulkV2Api")); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + data.grabExcessHorizontalSpace = true; + buttonUseBulkV2Api.setLayoutData(data); + buttonUseBulkV2Api.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + useBulkV2API = buttonUseBulkV2Api.getSelection(); + if (!useBulkV2API) { + return; + } + useSoapAPI = false; + useBulkAPI = false; + setAllApiOptions(); + + // get default batch size for Bulk v2 and set it + int newDefaultBatchSize = getController().getAppConfig().getDefaultImportBatchSize(true, true); + logger.debug("Setting batch size to " + newDefaultBatchSize); + textImportBatchSize.setText(String.valueOf(newDefaultBatchSize)); + String[] args = {getImportBatchLimitsURL(), + Integer.toString(appConfig.getMaxImportBatchSize(useBulkAPI || useBulkV2API, useBulkV2API))}; + labelImportBatchSize.setText( + Labels.getFormattedString(AdvancedSettingsDialog.class.getSimpleName() + ".uiLabel." + AppConfig.PROP_IMPORT_BATCH_SIZE, args)); + labelImportBatchSize.redraw(); + } + }); + + + // SOAP API - Keep Account team setting + this.soapApiOptionsComposite = new Composite(restComp, SWT.None); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + data.grabExcessHorizontalSpace = true; + this.soapApiOptionsComposite.setLayoutData(data); + layout = new GridLayout(2, true); + layout.verticalSpacing = 10; + this.soapApiOptionsComposite.setLayout(layout); + + createLink(soapApiOptionsComposite, null, null, AppConfig.PROP_PROCESS_KEEP_ACCOUNT_TEAM); + boolean keepAccountTeam = appConfig.getBoolean(AppConfig.PROP_PROCESS_KEEP_ACCOUNT_TEAM); + buttonKeepAccountTeam = new Button(this.soapApiOptionsComposite, SWT.CHECK); + buttonKeepAccountTeam.setSelection(keepAccountTeam); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + data.grabExcessHorizontalSpace = true; + buttonKeepAccountTeam.setLayoutData(data); + buttonKeepAccountTeam.setToolTipText(Labels.getString("AdvancedSettingsDialog.uiTooltip." + AppConfig.PROP_PROCESS_KEEP_ACCOUNT_TEAM)); + + // update using external id + labelUpdateWithExternalId = createLabel(soapApiOptionsComposite, null, null, AppConfig.PROP_UPDATE_WITH_EXTERNALID); + boolean updateWithExternalId = appConfig.getBoolean(AppConfig.PROP_UPDATE_WITH_EXTERNALID); + buttonUpdateWithExternalId = new Button(this.soapApiOptionsComposite, SWT.CHECK); + buttonUpdateWithExternalId.setSelection(updateWithExternalId); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + data.grabExcessHorizontalSpace = true; + buttonUpdateWithExternalId.setLayoutData(data); + buttonUpdateWithExternalId.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + setEnabled(soapApiOptionsComposite, true); + } + }); //insert Nulls - Label labelNulls = new Label(restComp, SWT.RIGHT); - labelNulls.setText(Labels.getString("AdvancedSettingsDialog.insertNulls")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelNulls.setLayoutData(data); - buttonNulls = new Button(restComp, SWT.CHECK); - buttonNulls.setSelection(config.getBoolean(Config.INSERT_NULLS)); - - //assignment rules - Label labelRule = new Label(restComp, SWT.RIGHT); - labelRule.setText(Labels.getString("AdvancedSettingsDialog.assignmentRule")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelRule.setLayoutData(data); - - textRule = new Text(restComp, SWT.BORDER); - textRule.setTextLimit(18); - data = new GridData(); - data.widthHint = 115; - textRule.setLayoutData(data); - textRule.setText(config.getString(Config.ASSIGNMENT_RULE)); + labelNulls = createLabel(soapApiOptionsComposite, null, null, AppConfig.PROP_INSERT_NULLS); + buttonNulls = new Button(this.soapApiOptionsComposite, SWT.CHECK); + buttonNulls.setSelection(appConfig.getBoolean(AppConfig.PROP_INSERT_NULLS)); - //endpoint - Label labelEndpoint = new Label(restComp, SWT.RIGHT); - labelEndpoint.setText(Labels.getString("AdvancedSettingsDialog.serverURL")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelEndpoint.setLayoutData(data); + //Field truncation + labelTruncateFields = createLabel(soapApiOptionsComposite, null, null, AppConfig.PROP_TRUNCATE_FIELDS); + buttonTruncateFields = new Button(this.soapApiOptionsComposite, SWT.CHECK); + buttonTruncateFields.setSelection(appConfig.getBoolean(AppConfig.PROP_TRUNCATE_FIELDS)); + + //insert compression + createLabel(soapApiOptionsComposite, null, null, AppConfig.PROP_NO_COMPRESSION); + buttonCompression = new Button(soapApiOptionsComposite, SWT.CHECK); + buttonCompression.setSelection(appConfig.getBoolean(AppConfig.PROP_NO_COMPRESSION)); + buttonCompression.setToolTipText(Labels.getString("AdvancedSettingsDialog.uiTooltip." + AppConfig.PROP_NO_COMPRESSION)); - textEndpoint = new Text(restComp, SWT.BORDER); + //timeout size + createLabel(soapApiOptionsComposite, null, null, AppConfig.PROP_TIMEOUT_SECS); + textTimeout = new Text(soapApiOptionsComposite, SWT.BORDER); + textTimeout.setText(appConfig.getString(AppConfig.PROP_TIMEOUT_SECS)); + textTimeout.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent event) { + event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); + } + }); data = new GridData(); - data.widthHint = 250; - textEndpoint.setLayoutData(data); - String endpoint = config.getString(Config.ENDPOINT); - if ("".equals(endpoint)) { //$NON-NLS-1$ - endpoint = defaultServer; - } + textTimeout.setTextLimit(4); + GC gc = new GC(textTimeout); + Point textSize = gc.textExtent("8"); + gc.dispose(); + data.widthHint = 4 * textSize.x; + textTimeout.setLayoutData(data); + textTimeout.setToolTipText(Labels.getString("AdvancedSettingsDialog.uiTooltip." + AppConfig.PROP_TIMEOUT_SECS)); - textEndpoint.setText(endpoint); + // Bulk API serial concurrency mode setting + this.bulkApiOptionsComposite = new Composite(restComp, SWT.None); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + data.grabExcessHorizontalSpace = true; + this.bulkApiOptionsComposite.setLayoutData(data); + layout = new GridLayout(2, true); + layout.verticalSpacing = 10; + this.bulkApiOptionsComposite.setLayout(layout); - //reset url on login - Label labelResetUrl = new Label(restComp, SWT.RIGHT); - labelResetUrl.setText(Labels.getString("AdvancedSettingsDialog.resetUrlOnLogin")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelResetUrl.setLayoutData(data); - buttonResetUrl = new Button(restComp, SWT.CHECK); - buttonResetUrl.setSelection(config.getBoolean(Config.RESET_URL_ON_LOGIN)); + createLink(bulkApiOptionsComposite, null, null, AppConfig.PROP_BULK_API_SERIAL_MODE); + buttonBulkApiSerialMode = new Button(this.bulkApiOptionsComposite, SWT.CHECK); + buttonBulkApiSerialMode.setSelection(appConfig.getBoolean(AppConfig.PROP_BULK_API_SERIAL_MODE)); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + data.grabExcessHorizontalSpace = true; + buttonBulkApiSerialMode.setLayoutData(data); - //insert compression - Label labelCompression = new Label(restComp, SWT.RIGHT); - labelCompression.setText(Labels.getString("AdvancedSettingsDialog.compression")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelCompression.setLayoutData(data); - buttonCompression = new Button(restComp, SWT.CHECK); - buttonCompression.setSelection(config.getBoolean(Config.NO_COMPRESSION)); + // SOAP and Bulk API zip content setting + this.zipContentComposite = new Composite(restComp, SWT.None); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + data.grabExcessHorizontalSpace = true; + this.zipContentComposite.setLayoutData(data); + layout = new GridLayout(2, true); + layout.verticalSpacing = 10; + this.zipContentComposite.setLayout(layout); - //timeout size - Label labelTimeout = new Label(restComp, SWT.RIGHT); - labelTimeout.setText(Labels.getString("AdvancedSettingsDialog.timeout")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelTimeout.setLayoutData(data); + createLink(zipContentComposite, null, null, AppConfig.PROP_BULK_API_ZIP_CONTENT); + buttonBulkApiZipContent = new Button(zipContentComposite, SWT.CHECK); + buttonBulkApiZipContent.setSelection(appConfig.getBoolean(AppConfig.PROP_BULK_API_ZIP_CONTENT)); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + data.grabExcessHorizontalSpace = true; + buttonBulkApiZipContent.setLayoutData(data); - textTimeout = new Text(restComp, SWT.BORDER); - textTimeout.setTextLimit(4); - textTimeout.setText(config.getString(Config.TIMEOUT_SECS)); - textTimeout.addVerifyListener(new VerifyListener() { + //SOAP and Bulk API - batch size + this.importBatchSizeComposite = new Composite(restComp, SWT.None); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + data.grabExcessHorizontalSpace = true; + this.importBatchSizeComposite.setLayoutData(data); + layout = new GridLayout(2, true); + layout.verticalSpacing = 10; + this.importBatchSizeComposite.setLayout(layout); + + String[] args = {getImportBatchLimitsURL(), + Integer.toString(appConfig.getMaxImportBatchSize(useBulkAPI || useBulkV2API, useBulkV2API))}; + labelImportBatchSize = createLink(importBatchSizeComposite, null, args, AppConfig.PROP_IMPORT_BATCH_SIZE); + textImportBatchSize = new Text(importBatchSizeComposite, SWT.BORDER); + textImportBatchSize.setText(Integer.toString(appConfig.getMaxRowsInImportBatch())); + textImportBatchSize.setEnabled(!useBulkV2API); + textImportBatchSize.addVerifyListener(new VerifyListener() { @Override public void verifyText(VerifyEvent event) { event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); } }); data = new GridData(); - data.widthHint = 30; - textTimeout.setLayoutData(data); + textImportBatchSize.setTextLimit(8); + data.widthHint = 8 * textSize.x; + textImportBatchSize.setLayoutData(data); - //extraction batch size - Label labelQueryBatch = new Label(restComp, SWT.RIGHT); - labelQueryBatch.setText(Labels.getString("ExtractionInputDialog.querySize")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelQueryBatch.setLayoutData(data); - - textQueryBatch = new Text(restComp, SWT.BORDER); - textQueryBatch.setText(config.getString(Config.EXTRACT_REQUEST_SIZE)); - textQueryBatch.setTextLimit(4); - textQueryBatch.addVerifyListener(new VerifyListener() { + //SOAP API - extraction batch size + this.exportBatchSizeComposite = new Composite(restComp, SWT.None); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + data.grabExcessHorizontalSpace = true; + this.exportBatchSizeComposite.setLayoutData(data); + layout = new GridLayout(2, true); + layout.verticalSpacing = 10; + this.exportBatchSizeComposite.setLayout(layout); + + args = new String[]{Integer.toString(AppConfig.MIN_EXPORT_BATCH_SIZE), + Integer.toString(AppConfig.MAX_EXPORT_BATCH_SIZE)}; + createLink(exportBatchSizeComposite, null, args, AppConfig.PROP_EXPORT_BATCH_SIZE); + textExportBatchSize = new Text(exportBatchSizeComposite, SWT.BORDER); + textExportBatchSize.setText(appConfig.getString(AppConfig.PROP_EXPORT_BATCH_SIZE)); + textExportBatchSize.addVerifyListener(new VerifyListener() { @Override public void verifyText(VerifyEvent event) { event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); } }); data = new GridData(); - data.widthHint = 30; - textQueryBatch.setLayoutData(data); + textExportBatchSize.setTextLimit(4); + data.widthHint = 4 * textSize.x; + textExportBatchSize.setLayoutData(data); + + createLink(restComp, "undeleteOperationEnabled", null, null); + buttonUndeleteEnabled = new Button(restComp, SWT.CHECK); + // user can't check/uncheck the button + buttonUndeleteEnabled.setEnabled(false); + buttonUndeleteEnabled.setSelection(useSoapAPI); - //enable/disable output of success file for extracts - Label labelOutputExtractStatus = new Label(restComp, SWT.RIGHT); - labelOutputExtractStatus.setText(Labels.getString("AdvancedSettingsDialog.outputExtractStatus")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelOutputExtractStatus.setLayoutData(data); + createLink(restComp, "hardDeleteOperationEnabled", null, null); + buttonHardDeleteEnabled = new Button(restComp, SWT.CHECK); + // user can't check/uncheck the button + buttonHardDeleteEnabled.setEnabled(false); + buttonHardDeleteEnabled.setSelection(!useSoapAPI); + initializeAllApiOptions(); + + labelSeparator = new Label(restComp, SWT.SEPARATOR | SWT.HORIZONTAL); + data = new GridData(GridData.FILL_HORIZONTAL); + data.horizontalIndent = 100; + data.horizontalSpan = 2; + labelSeparator.setLayoutData(data); + + blank = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + data.heightHint = 15; + blank.setLayoutData(data); + + //assignment rules + createLink(restComp, null, null, AppConfig.PROP_ASSIGNMENT_RULE); + textRule = new Text(restComp, SWT.BORDER); + data = new GridData(); + textRule.setTextLimit(18); + data.widthHint = 18 * textSize.x; + textRule.setLayoutData(data); + textRule.setText(appConfig.getString(AppConfig.PROP_ASSIGNMENT_RULE)); + textRule.setToolTipText(Labels.getString("AdvancedSettingsDialog.uiTooltip." + AppConfig.PROP_ASSIGNMENT_RULE)); + + //endpoints + createLink(restComp, null, null, AppConfig.PROP_AUTH_ENDPOINT_PROD); + textProdEndpoint = new Text(restComp, SWT.BORDER); + data = new GridData(GridData.FILL_HORIZONTAL); + data.widthHint = 30 * textSize.x; + textProdEndpoint.setLayoutData(data); + String endpoint = appConfig.getString(AppConfig.PROP_AUTH_ENDPOINT_PROD); + // try with legacy endpoint property + if (endpoint == null + || endpoint.isBlank() + || endpoint.startsWith(AppConfig.DEFAULT_ENDPOINT_URL_PROD)) { + endpoint = appConfig.getString(AppConfig.PROP_AUTH_ENDPOINT_LEGACY); + } + if ("".equals(endpoint)) { //$NON-NLS-1$ + endpoint = AppConfig.DEFAULT_ENDPOINT_URL_PROD; + } + textProdEndpoint.setText(endpoint); + + createLink(restComp, null, null, AppConfig.PROP_AUTH_ENDPOINT_SANDBOX); + textSBEndpoint = new Text(restComp, SWT.BORDER); + data = new GridData(GridData.FILL_HORIZONTAL); + data.widthHint = 30 * textSize.x; + textSBEndpoint.setLayoutData(data); + endpoint = appConfig.getString(AppConfig.PROP_AUTH_ENDPOINT_SANDBOX); + // try with legacy endpoint property + if (endpoint == null + || endpoint.isBlank() + || endpoint.startsWith(AppConfig.DEFAULT_ENDPOINT_URL_SANDBOX)) { + endpoint = appConfig.getString(AppConfig.PROP_AUTH_ENDPOINT_LEGACY); + } + if ("".equals(endpoint)) { //$NON-NLS-1$ + endpoint = AppConfig.DEFAULT_ENDPOINT_URL_SANDBOX; + } + textSBEndpoint.setText(endpoint); + + // enable/disable sort of fields to extract + createLabel(restComp, null, null, AppConfig.PROP_SORT_EXTRACT_FIELDS); + buttonSortExtractFields = new Button(restComp, SWT.CHECK); + buttonSortExtractFields.setSelection(appConfig.getBoolean(AppConfig.PROP_SORT_EXTRACT_FIELDS)); + + // enable/disable limiting query result columns to fields specified in the SOQL query + createLabel(restComp, null, null, AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS); + buttonLimitQueryResultColumnsToFieldsInQuery = new Button(restComp, SWT.CHECK); + buttonLimitQueryResultColumnsToFieldsInQuery.setSelection(appConfig.getBoolean(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS)); + + //enable/disable output of success file for extracts + createLabel(restComp, null, null, AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT); buttonOutputExtractStatus = new Button(restComp, SWT.CHECK); - buttonOutputExtractStatus.setSelection(config.getBoolean(Config.ENABLE_EXTRACT_STATUS_OUTPUT)); + buttonOutputExtractStatus.setSelection(appConfig.getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT)); //utf-8 for loading - Label labelReadUTF8 = new Label(restComp, SWT.RIGHT); - labelReadUTF8.setText(Labels.getString("AdvancedSettingsDialog.readUTF8")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelReadUTF8.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_READ_UTF8); buttonReadUtf8 = new Button(restComp, SWT.CHECK); - buttonReadUtf8.setSelection(config.getBoolean(Config.READ_UTF8)); + buttonReadUtf8.setSelection(appConfig.getBoolean(AppConfig.PROP_READ_UTF8)); //utf-8 for extraction - Label labelWriteUTF8 = new Label(restComp, SWT.RIGHT); - labelWriteUTF8.setText(Labels.getString("AdvancedSettingsDialog.writeUTF8")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelWriteUTF8.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_WRITE_UTF8); buttonWriteUtf8 = new Button(restComp, SWT.CHECK); - buttonWriteUtf8.setSelection(config.getBoolean(Config.WRITE_UTF8)); + buttonWriteUtf8.setSelection(appConfig.getBoolean(AppConfig.PROP_WRITE_UTF8)); //European Dates - Label labelEuropeanDates = new Label(restComp, SWT.RIGHT); - labelEuropeanDates.setText(Labels.getString("AdvancedSettingsDialog.useEuropeanDateFormat")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelEuropeanDates.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_EURO_DATES); buttonEuroDates = new Button(restComp, SWT.CHECK); - buttonEuroDates.setSelection(config.getBoolean(Config.EURO_DATES)); - - //Field truncation - Label labelTruncateFields = new Label(restComp, SWT.RIGHT); - labelTruncateFields.setText(Labels.getString("AdvancedSettingsDialog.allowFieldTruncation")); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelTruncateFields.setLayoutData(data); + buttonEuroDates.setSelection(appConfig.getBoolean(AppConfig.PROP_EURO_DATES)); - buttonTruncateFields = new Button(restComp, SWT.CHECK); - buttonTruncateFields.setSelection(config.getBoolean(Config.TRUNCATE_FIELDS)); + //format phone fields on the client side + createLabel(restComp, null, null, AppConfig.PROP_FORMAT_PHONE_FIELDS); + buttonFormatPhoneFields = new Button(restComp, SWT.CHECK); + buttonFormatPhoneFields.setSelection(appConfig.getBoolean(AppConfig.PROP_FORMAT_PHONE_FIELDS)); - Label labelCsvCommand = new Label(restComp, SWT.RIGHT); - labelCsvCommand.setText(Labels.getString("AdvancedSettingsDialog.useCommaAsCsvDelimiter")); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelCsvCommand.setLayoutData(data); + createLabel(restComp, null, null, AppConfig.PROP_CSV_DELIMITER_COMMA); buttonCsvComma = new Button(restComp, SWT.CHECK); - buttonCsvComma.setSelection(config.getBoolean(Config.CSV_DELIMETER_COMMA)); + buttonCsvComma.setSelection(appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_COMMA)); - Label labelTabCommand = new Label(restComp, SWT.RIGHT); - labelTabCommand.setText(Labels.getString("AdvancedSettingsDialog.useTabAsCsvDelimiter")); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelTabCommand.setLayoutData(data); + createLabel(restComp, null, null, AppConfig.PROP_CSV_DELIMITER_TAB); buttonCsvTab = new Button(restComp, SWT.CHECK); - buttonCsvTab.setSelection(config.getBoolean(Config.CSV_DELIMETER_TAB)); - - Label labelOtherCommand = new Label(restComp, SWT.RIGHT); - labelOtherCommand.setText(Labels.getString("AdvancedSettingsDialog.useOtherAsCsvDelimiter")); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelOtherCommand.setLayoutData(data); - buttonCsvOther = new Button(restComp, SWT.CHECK); - buttonCsvOther.setSelection(config.getBoolean(Config.CSV_DELIMETER_OTHER)); + buttonCsvTab.setSelection(appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_TAB)); - Label labelOtherDelimiterValue = new Label(restComp, SWT.RIGHT); - labelOtherDelimiterValue.setText(Labels.getString("AdvancedSettingsDialog.csvOtherDelimiterValue")); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelOtherDelimiterValue.setLayoutData(data); - textSplitterValue = new Text(restComp, SWT.BORDER); - textSplitterValue.setText(config.getString(Config.CSV_DELIMETER_OTHER_VALUE)); + createLabel(restComp, null, null, AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE); + textUploadCSVDelimiterValue = new Text(restComp, SWT.BORDER); + textUploadCSVDelimiterValue.setText(appConfig.getString(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE)); data = new GridData(); - data.widthHint = 25; - textSplitterValue.setLayoutData(data); - - // Enable Bulk API Setting - Label labelUseBulkApi = new Label(restComp, SWT.RIGHT); - labelUseBulkApi.setText(Labels.getString("AdvancedSettingsDialog.useBulkApi")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelUseBulkApi.setLayoutData(data); - - boolean useBulkAPI = config.getBoolean(Config.BULK_API_ENABLED); - buttonUseBulkApi = new Button(restComp, SWT.CHECK); - buttonUseBulkApi.setSelection(useBulkAPI); - buttonUseBulkApi.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - boolean enabled = buttonUseBulkApi.getSelection(); - // update batch size when this setting changes - int newDefaultBatchSize = controller.getConfig().getDefaultBatchSize(enabled); - logger.info("Setting batch size to " + newDefaultBatchSize); - textBatch.setText(String.valueOf(newDefaultBatchSize)); - // make sure the appropriate check boxes are enabled or disabled - initBulkApiSetting(enabled); - } - }); + data.widthHint = 15 * textSize.x; + textUploadCSVDelimiterValue.setLayoutData(data); - // Bulk API serial concurrency mode setting - Label labelBulkApiSerialMode = new Label(restComp, SWT.RIGHT); - labelBulkApiSerialMode.setText(Labels.getString("AdvancedSettingsDialog.bulkApiSerialMode")); //$NON-NLS-1$ - labelBulkApiSerialMode.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); - - buttonBulkApiSerialMode = new Button(restComp, SWT.CHECK); - buttonBulkApiSerialMode.setSelection(config.getBoolean(Config.BULK_API_SERIAL_MODE)); - buttonBulkApiSerialMode.setEnabled(useBulkAPI); + createLabel(restComp, null, null, AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS); + textQueryResultsDelimiterValue = new Text(restComp, SWT.BORDER); + textQueryResultsDelimiterValue.setText(appConfig.getString(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS)); + textQueryResultsDelimiterValue.setTextLimit(1); + data = new GridData(); + data.widthHint = 5 * textSize.x; + textQueryResultsDelimiterValue.setLayoutData(data); + + + // include image data for Rich Text Fields in query results + // Config.INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS + createLabel(restComp, null, null, AppConfig.PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS); + boolean includeRTFBinaryDataInQueryResults = appConfig.getBoolean(AppConfig.PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS); + buttonIncludeRTFBinaryDataInQueryResults = new Button(restComp, SWT.CHECK); + buttonIncludeRTFBinaryDataInQueryResults.setSelection(includeRTFBinaryDataInQueryResults); + + // Cache DescribeGlobal results across operations + createLabel(restComp, null, null, AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS); + boolean cacheDescribeGlobalResults = appConfig.getBoolean(AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS); + buttonCacheDescribeGlobalResults = new Button(restComp, SWT.CHECK); + buttonCacheDescribeGlobalResults.setSelection(cacheDescribeGlobalResults); + + Label empty = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + empty.setLayoutData(data); - // Bulk API serial concurrency mode setting - Label labelBulkApiZipContent = new Label(restComp, SWT.RIGHT); - labelBulkApiZipContent.setText(Labels.getString("AdvancedSettingsDialog.bulkApiZipContent")); //$NON-NLS-1$ - labelBulkApiZipContent.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); + empty = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + empty.setLayoutData(data); - buttonBulkApiZipContent = new Button(restComp, SWT.CHECK); - buttonBulkApiZipContent.setSelection(config.getBoolean(Config.BULK_API_SERIAL_MODE)); - buttonBulkApiZipContent.setEnabled(useBulkAPI); // timezone - textTimezone = createTextInput(restComp, "AdvancedSettingsDialog.timezone", Config.TIMEZONE, TimeZone.getDefault().getID(), 200); - + textTimezone = createTimezoneTextInput(restComp, AppConfig.PROP_TIMEZONE, TimeZone.getDefault().getID(), 30 * textSize.x); + + empty = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + empty.setLayoutData(data); + // proxy Host - Label labelProxyHost = new Label(restComp, SWT.RIGHT); - labelProxyHost.setText(Labels.getString("AdvancedSettingsDialog.proxyHost")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyHost.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_PROXY_HOST); textProxyHost = new Text(restComp, SWT.BORDER); - textProxyHost.setText(config.getString(Config.PROXY_HOST)); - data = new GridData(); - data.widthHint = 250; + textProxyHost.setText(appConfig.getString(AppConfig.PROP_PROXY_HOST)); + data = new GridData(GridData.FILL_HORIZONTAL); textProxyHost.setLayoutData(data); //Proxy Port - Label labelProxyPort = new Label(restComp, SWT.RIGHT); - labelProxyPort.setText(Labels.getString("AdvancedSettingsDialog.proxyPort")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyPort.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_PROXY_PORT); textProxyPort = new Text(restComp, SWT.BORDER); - textProxyPort.setText(config.getString(Config.PROXY_PORT)); - textProxyPort.setTextLimit(4); + textProxyPort.setText(appConfig.getString(AppConfig.PROP_PROXY_PORT)); textProxyPort.addVerifyListener(new VerifyListener() { @Override public void verifyText(VerifyEvent event) { @@ -519,46 +758,74 @@ public void verifyText(VerifyEvent event) { } }); data = new GridData(); - data.widthHint = 25; + textProxyPort.setTextLimit(5); + data.widthHint = 5 * textSize.x; textProxyPort.setLayoutData(data); //Proxy Username - Label labelProxyUsername = new Label(restComp, SWT.RIGHT); - labelProxyUsername.setText(Labels.getString("AdvancedSettingsDialog.proxyUser")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyUsername.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_PROXY_USERNAME); textProxyUsername = new Text(restComp, SWT.BORDER); - textProxyUsername.setText(config.getString(Config.PROXY_USERNAME)); + textProxyUsername.setText(appConfig.getString(AppConfig.PROP_PROXY_USERNAME)); data = new GridData(); - data.widthHint = 120; + data.widthHint = 20 * textSize.x; textProxyUsername.setLayoutData(data); //Proxy Password - Label labelProxyPassword = new Label(restComp, SWT.RIGHT); - labelProxyPassword.setText(Labels.getString("AdvancedSettingsDialog.proxyPassword")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyPassword.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_PROXY_PASSWORD); textProxyPassword = new Text(restComp, SWT.BORDER | SWT.PASSWORD); - textProxyPassword.setText(config.getString(Config.PROXY_PASSWORD)); + textProxyPassword.setText(appConfig.getString(AppConfig.PROP_PROXY_PASSWORD)); data = new GridData(); - data.widthHint = 120; + data.widthHint = 20 * textSize.x; textProxyPassword.setLayoutData(data); - //proxy NTLM domain - Label labelProxyNtlmDomain = new Label(restComp, SWT.RIGHT); - labelProxyNtlmDomain.setText(Labels.getString("AdvancedSettingsDialog.proxyNtlmDomain")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyNtlmDomain.setLayoutData(data); - + createLabel(restComp, null, null, AppConfig.PROP_PROXY_NTLM_DOMAIN); textProxyNtlmDomain = new Text(restComp, SWT.BORDER); - textProxyNtlmDomain.setText(config.getString(Config.PROXY_NTLM_DOMAIN)); - data = new GridData(); - data.widthHint = 250; + textProxyNtlmDomain.setText(appConfig.getString(AppConfig.PROP_PROXY_NTLM_DOMAIN)); + data = new GridData(GridData.FILL_HORIZONTAL); textProxyNtlmDomain.setLayoutData(data); - + + empty = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + empty.setLayoutData(data); + + createLabel(restComp, null, null, AppConfig.PROP_OAUTH_LOGIN_FROM_BROWSER); + boolean doLoginFromBrowser = appConfig.getBoolean(AppConfig.PROP_OAUTH_LOGIN_FROM_BROWSER); + buttonLoginFromBrowser = new Button(restComp, SWT.CHECK); + buttonLoginFromBrowser.setSelection(doLoginFromBrowser); + + createLabel(restComp, null, null, + appConfig.getOAuthEnvironmentPropertyName(AppConfig.SERVER_PROD_ENVIRONMENT_VAL, AppConfig.PARTNER_CLIENTID_LITERAL)); + this.textProductionPartnerClientID = new Text(restComp, SWT.NONE); + data = new GridData(GridData.FILL_HORIZONTAL); + textProductionPartnerClientID.setLayoutData(data); + String clientId = appConfig.getOAuthEnvironmentString(AppConfig.SERVER_PROD_ENVIRONMENT_VAL, AppConfig.PARTNER_CLIENTID_LITERAL); + this.textProductionPartnerClientID.setText(clientId); + + createLabel(restComp, null, null, + appConfig.getOAuthEnvironmentPropertyName(AppConfig.SERVER_PROD_ENVIRONMENT_VAL, AppConfig.BULK_CLIENTID_LITERAL)); + this.textProductionBulkClientID = new Text(restComp, SWT.NONE); + data = new GridData(GridData.FILL_HORIZONTAL); + textProductionBulkClientID.setLayoutData(data); + clientId = appConfig.getOAuthEnvironmentString(AppConfig.SERVER_PROD_ENVIRONMENT_VAL, AppConfig.BULK_CLIENTID_LITERAL); + this.textProductionBulkClientID.setText(clientId); + + createLabel(restComp, null, null, + appConfig.getOAuthEnvironmentPropertyName(AppConfig.SERVER_SB_ENVIRONMENT_VAL, AppConfig.PARTNER_CLIENTID_LITERAL)); + this.textSandboxPartnerClientID = new Text(restComp, SWT.NONE); + data = new GridData(GridData.FILL_HORIZONTAL); + textSandboxPartnerClientID.setLayoutData(data); + clientId = appConfig.getOAuthEnvironmentString(AppConfig.SERVER_SB_ENVIRONMENT_VAL, AppConfig.PARTNER_CLIENTID_LITERAL); + this.textSandboxPartnerClientID.setText(clientId); + + createLabel(restComp, null, null, + appConfig.getOAuthEnvironmentPropertyName(AppConfig.SERVER_SB_ENVIRONMENT_VAL, AppConfig.BULK_CLIENTID_LITERAL)); + this.textSandboxBulkClientID = new Text(restComp, SWT.NONE); + data = new GridData(GridData.FILL_HORIZONTAL); + textSandboxBulkClientID.setLayoutData(data); + clientId = appConfig.getOAuthEnvironmentString(AppConfig.SERVER_SB_ENVIRONMENT_VAL, AppConfig.BULK_CLIENTID_LITERAL); + this.textSandboxBulkClientID.setText(clientId); ////////////////////////////////////////////////// //Row to start At @@ -566,29 +833,25 @@ public void verifyText(VerifyEvent event) { data = new GridData(); data.horizontalSpan = 2; blankAgain.setLayoutData(data); - - //Row to start AT - Label labelLastRow = new Label(restComp, SWT.NONE); - - String lastBatch = controller.getConfig().getString(LastRun.LAST_LOAD_BATCH_ROW); + + String lastBatch = getController().getAppConfig().getString(LastRunProperties.LAST_LOAD_BATCH_ROW); if (lastBatch.equals("")) { //$NON-NLS-1$ lastBatch = "0"; //$NON-NLS-1$ } - labelLastRow.setText(Labels.getFormattedString("AdvancedSettingsDialog.lastBatch", lastBatch)); //$NON-NLS-1$ - data = new GridData(); - data.horizontalSpan = 2; - labelLastRow.setLayoutData(data); - - Label labelRowToStart = new Label(restComp, SWT.RIGHT); - labelRowToStart.setText(Labels.getString("AdvancedSettingsDialog.startRow")); //$NON-NLS-1$ + Text labelRowToStart = createLabel(restComp, null, null, AppConfig.PROP_LOAD_ROW_TO_START_AT); + labelRowToStart.setText(Labels.getString("AdvancedSettingsDialog.uiLabel." + AppConfig.PROP_LOAD_ROW_TO_START_AT) + + "\n(" + + Labels.getFormattedString("AdvancedSettingsDialog.uiLabel." + LastRunProperties.LAST_LOAD_BATCH_ROW, lastBatch) + + ")"); //$NON-NLS-1$ data = new GridData(GridData.HORIZONTAL_ALIGN_END); labelRowToStart.setLayoutData(data); textRowToStart = new Text(restComp, SWT.BORDER); - textRowToStart.setText(config.getString(Config.LOAD_ROW_TO_START_AT)); + textRowToStart.setText(appConfig.getString(AppConfig.PROP_LOAD_ROW_TO_START_AT)); data = new GridData(); - data.widthHint = 75; + textRowToStart.setTextLimit(15); + data.widthHint = 15 * textSize.x; textRowToStart.setLayoutData(data); textRowToStart.addVerifyListener(new VerifyListener() { @Override @@ -599,22 +862,112 @@ public void verifyText(VerifyEvent event) { // now that we've created all the buttons, make sure that buttons dependent on the bulk api // setting are enabled or disabled appropriately - initBulkApiSetting(useBulkAPI); + // enableBulkRelatedOptions(useBulkAPI); + + blankAgain = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + blankAgain.setLayoutData(data); + + createLabel(restComp, null, null, AppConfig.PROP_WIZARD_CLOSE_ON_FINISH); + boolean closeWizardOnFinish = appConfig.getBoolean(AppConfig.PROP_WIZARD_CLOSE_ON_FINISH); + buttonCloseWizardOnFinish = new Button(restComp, SWT.CHECK); + buttonCloseWizardOnFinish.setSelection(closeWizardOnFinish); + + createLabel(restComp, "wizardWidthAndHeight", null, null); + Composite widthAndHeightComp = new Composite(restComp, SWT.NONE); + data = new GridData(GridData.FILL_BOTH); + widthAndHeightComp.setLayoutData(data); + layout = new GridLayout(3, false); + widthAndHeightComp.setLayout(layout); + textWizardWidth = new Text(widthAndHeightComp, SWT.BORDER); + textWizardWidth.setText(appConfig.getString(AppConfig.PROP_WIZARD_WIDTH)); + data = new GridData(); + textWizardWidth.setTextLimit(4); + data.widthHint = 4 * textSize.x; + textWizardWidth.setLayoutData(data); + textWizardWidth.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent event) { + event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); + } + }); + + Label labelMultiplySymbol = new Label(widthAndHeightComp, SWT.CENTER); + labelMultiplySymbol.setText("x"); + labelMultiplySymbol.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_CENTER)); + + textWizardHeight = new Text(widthAndHeightComp, SWT.BORDER); + textWizardHeight.setText(appConfig.getString(AppConfig.PROP_WIZARD_HEIGHT)); + textWizardHeight.setTextLimit(4); + data.widthHint = 4 * textSize.x; + textWizardHeight.setLayoutData(data); + textWizardHeight.addVerifyListener(new VerifyListener() { + @Override + public void verifyText(VerifyEvent event) { + event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); + } + }); + + createLabel(restComp, null, null, AppConfig.PROP_WIZARD_POPULATE_RESULTS_FOLDER_WITH_PREVIOUS_OP_RESULTS_FOLDER); + boolean populateResultsFolderOnFinishStep = appConfig.getBoolean(AppConfig.PROP_WIZARD_POPULATE_RESULTS_FOLDER_WITH_PREVIOUS_OP_RESULTS_FOLDER); + buttonPopulateResultsFolderOnWizardFinishStep = new Button(restComp, SWT.CHECK); + buttonPopulateResultsFolderOnWizardFinishStep.setSelection(populateResultsFolderOnFinishStep); + + blankAgain = new Label(restComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + blankAgain.setLayoutData(data); + + createLabel(restComp, "configDir", null, null); + Text textConfigDirLocation = new Text(restComp, SWT.LEFT | SWT.READ_ONLY); + textConfigDirLocation.setText(AppConfig.getConfigurationsDir()); //$NON-NLS-1$ + + createLabel(restComp, "loggingConfigFile", null, null); + String log4j2ConfFile = LoggingUtil.getLoggingConfigFile(); + Text textLoggingFileName = new Text(restComp, SWT.LEFT | SWT.READ_ONLY); + textLoggingFileName.setText(log4j2ConfFile); //$NON-NLS-1$ + + createLink(restComp, "loggingLevel", null, null); + comboLoggingLevelDropdown = new Combo(restComp, SWT.DROP_DOWN); + comboLoggingLevelDropdown.setItems(LOGGING_LEVEL); + String currentLoggingLevel = LoggingUtil.getLoggingLevel().toUpperCase(); + if (currentLoggingLevel == null || currentLoggingLevel.isBlank()) { + currentLoggingLevel= LoggingUtil.getLoggingLevel(); + } + int currentLoggingLevelIndex = 0; + for (String level : LOGGING_LEVEL) { + if (currentLoggingLevel.equals(level)) { + break; + } + currentLoggingLevelIndex++; + } + if (currentLoggingLevelIndex == LOGGING_LEVEL.length) { + currentLoggingLevelIndex = 1; + } + comboLoggingLevelDropdown.select(currentLoggingLevelIndex); + if (log4j2ConfFile == null || !log4j2ConfFile.endsWith(".properties")) { + comboLoggingLevelDropdown.setEnabled(false); // Can't modify current setting + } + + createLabel(restComp, "latestLoggingFile", null, null); + Text textLoggingFileLocation = new Text(restComp, SWT.LEFT | SWT.READ_ONLY); + textLoggingFileLocation.setText(LoggingUtil.getLatestLoggingFile()); //$NON-NLS-1$ //the bottow separator - Label labelSeparatorBottom = new Label(restComp, SWT.SEPARATOR | SWT.HORIZONTAL); + Label labelSeparatorBottom = new Label(sc, SWT.SEPARATOR | SWT.HORIZONTAL); data = new GridData(GridData.FILL_HORIZONTAL); data.horizontalSpan = 2; labelSeparatorBottom.setLayoutData(data); //ok cancel buttons - new Label(restComp, SWT.NONE); + new Label(sc, SWT.NONE); // END MIDDLE COMPONENT // START BOTTOM COMPONENT - Composite buttonComp = new Composite(restComp, SWT.NONE); + Composite buttonComp = new Composite(shell, SWT.NONE); data = new GridData(GridData.HORIZONTAL_ALIGN_END); buttonComp.setLayoutData(data); buttonComp.setLayout(new GridLayout(2, false)); @@ -624,54 +977,139 @@ public void verifyText(VerifyEvent event) { // to the entered value Button ok = new Button(buttonComp, SWT.PUSH | SWT.FLAT); ok.setText(Labels.getString("UI.ok")); //$NON-NLS-1$ + ok.setEnabled(!appConfig.getBoolean(AppConfig.PROP_READ_ONLY_CONFIG_PROPERTIES)); ok.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - Config config = controller.getConfig(); + AppConfig appConfig = getController().getAppConfig(); + + String currentTextProdEndpoint = textProdEndpoint.getText(); + currentTextProdEndpoint = AppUtil.getURLStrFromDomainName(currentTextProdEndpoint); + if (currentTextProdEndpoint != null + && !currentTextProdEndpoint.isEmpty() + && !AppUtil.isValidHttpsUrl(currentTextProdEndpoint)) { + MessageDialog alert = new MessageDialog(getParent().getShell(), "Warning", null, + Labels.getFormattedString("AdvancedSettingsDialog.serverURLInfo", currentTextProdEndpoint), + MessageDialog.ERROR, new String[]{"OK"}, 0); + alert.open(); + return; + } + String currentTextSBEndpoint = textSBEndpoint.getText(); + currentTextSBEndpoint = AppUtil.getURLStrFromDomainName(currentTextSBEndpoint); + if (currentTextSBEndpoint != null + && !currentTextSBEndpoint.isEmpty() + && !AppUtil.isValidHttpsUrl(currentTextSBEndpoint)) { + MessageDialog alert = new MessageDialog(getParent().getShell(), "Warning", null, + Labels.getFormattedString("AdvancedSettingsDialog.serverURLInfo", currentTextSBEndpoint), + MessageDialog.ERROR, new String[]{"OK"}, 0); + alert.open(); + return; + + } //set the configValues - config.setValue(Config.HIDE_WELCOME_SCREEN, buttonHideWelcomeScreen.getSelection()); - config.setValue(Config.INSERT_NULLS, buttonNulls.getSelection()); - config.setValue(Config.LOAD_BATCH_SIZE, textBatch.getText()); + appConfig.setValue(AppConfig.PROP_HIDE_WELCOME_SCREEN, !buttonShowWelcomeScreen.getSelection()); + appConfig.setValue(AppConfig.PROP_SHOW_LOADER_UPGRADE_SCREEN, buttonShowLoaderUpgradeScreen.getSelection()); + appConfig.setValue(AppConfig.PROP_INSERT_NULLS, buttonNulls.getSelection()); + appConfig.setValue(AppConfig.PROP_IMPORT_BATCH_SIZE, textImportBatchSize.getText()); + boolean isOtherDelimiterSpecified = textUploadCSVDelimiterValue.getText() != null + && textUploadCSVDelimiterValue.getText().length() != 0; if (!buttonCsvComma.getSelection() && !buttonCsvTab.getSelection() - && (!buttonCsvOther.getSelection() - || textSplitterValue.getText() == null - || textSplitterValue.getText().length() == 0)) - { + && !isOtherDelimiterSpecified) { + MessageDialog alert = new MessageDialog(getParent().getShell(), "Warning", null, + Labels.getString("AdvancedSettingsDialog.checkUploadDelimiterCheckbox"), + MessageDialog.ERROR, new String[]{"OK"}, 0); + alert.open(); return; } - config.setValue(Config.CSV_DELIMETER_OTHER_VALUE, textSplitterValue.getText()); - config.setValue(Config.CSV_DELIMETER_COMMA, buttonCsvComma.getSelection()); - config.setValue(Config.CSV_DELIMETER_TAB, buttonCsvTab.getSelection()); - config.setValue(Config.CSV_DELIMETER_OTHER, buttonCsvOther.getSelection()); - - config.setValue(Config.EXTRACT_REQUEST_SIZE, textQueryBatch.getText()); - config.setValue(Config.ENDPOINT, textEndpoint.getText()); - config.setValue(Config.ASSIGNMENT_RULE, textRule.getText()); - config.setValue(Config.LOAD_ROW_TO_START_AT, textRowToStart.getText()); - config.setValue(Config.RESET_URL_ON_LOGIN, buttonResetUrl.getSelection()); - config.setValue(Config.NO_COMPRESSION, buttonCompression.getSelection()); - config.setValue(Config.TRUNCATE_FIELDS, buttonTruncateFields.getSelection()); - config.setValue(Config.TIMEOUT_SECS, textTimeout.getText()); - config.setValue(Config.ENABLE_EXTRACT_STATUS_OUTPUT, buttonOutputExtractStatus.getSelection()); - config.setValue(Config.READ_UTF8, buttonReadUtf8.getSelection()); - config.setValue(Config.WRITE_UTF8, buttonWriteUtf8.getSelection()); - config.setValue(Config.EURO_DATES, buttonEuroDates.getSelection()); - config.setValue(Config.TIMEZONE, textTimezone.getText()); - config.setValue(Config.PROXY_HOST, textProxyHost.getText()); - config.setValue(Config.PROXY_PASSWORD, textProxyPassword.getText()); - config.setValue(Config.PROXY_PORT, textProxyPort.getText()); - config.setValue(Config.PROXY_USERNAME, textProxyUsername.getText()); - config.setValue(Config.PROXY_NTLM_DOMAIN, textProxyNtlmDomain.getText()); - config.setValue(Config.BULK_API_ENABLED, buttonUseBulkApi.getSelection()); - config.setValue(Config.BULK_API_SERIAL_MODE, buttonBulkApiSerialMode.getSelection()); - config.setValue(Config.BULK_API_ZIP_CONTENT, buttonBulkApiZipContent.getSelection()); - - controller.saveConfig(); - controller.logout(); - - input = Labels.getString("UI.ok"); //$NON-NLS-1$ + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE, textUploadCSVDelimiterValue.getText()); + String queryResultsDelimiterStr = textQueryResultsDelimiterValue.getText(); + if (queryResultsDelimiterStr.length() == 0) { + queryResultsDelimiterStr = AppUtil.COMMA; // set to default + } + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS, queryResultsDelimiterStr); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_COMMA, buttonCsvComma.getSelection()); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_TAB, buttonCsvTab.getSelection()); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER, isOtherDelimiterSpecified); + + appConfig.setValue(AppConfig.PROP_EXPORT_BATCH_SIZE, textExportBatchSize.getText()); + appConfig.setAuthEndpointForEnv(currentTextProdEndpoint, AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + appConfig.setAuthEndpointForEnv(currentTextSBEndpoint, AppConfig.SERVER_SB_ENVIRONMENT_VAL); + appConfig.setValue(AppConfig.PROP_ASSIGNMENT_RULE, textRule.getText()); + appConfig.setValue(AppConfig.PROP_LOAD_ROW_TO_START_AT, textRowToStart.getText()); + appConfig.setValue(AppConfig.PROP_NO_COMPRESSION, buttonCompression.getSelection()); + appConfig.setValue(AppConfig.PROP_TRUNCATE_FIELDS, buttonTruncateFields.getSelection()); + appConfig.setValue(AppConfig.PROP_FORMAT_PHONE_FIELDS, buttonFormatPhoneFields.getSelection()); + appConfig.setValue(AppConfig.PROP_TIMEOUT_SECS, textTimeout.getText()); + appConfig.setValue(AppConfig.PROP_SORT_EXTRACT_FIELDS, buttonSortExtractFields.getSelection()); + appConfig.setValue(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS, buttonLimitQueryResultColumnsToFieldsInQuery.getSelection()); + appConfig.setValue(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT, buttonOutputExtractStatus.getSelection()); + appConfig.setValue(AppConfig.PROP_READ_UTF8, buttonReadUtf8.getSelection()); + appConfig.setValue(AppConfig.PROP_WRITE_UTF8, buttonWriteUtf8.getSelection()); + appConfig.setValue(AppConfig.PROP_EURO_DATES, buttonEuroDates.getSelection()); + appConfig.setValue(AppConfig.PROP_TIMEZONE, textTimezone.getText()); + appConfig.setValue(AppConfig.PROP_PROXY_HOST, textProxyHost.getText()); + appConfig.setValue(AppConfig.PROP_PROXY_PASSWORD, textProxyPassword.getText()); + appConfig.setValue(AppConfig.PROP_PROXY_PORT, textProxyPort.getText()); + appConfig.setValue(AppConfig.PROP_PROXY_USERNAME, textProxyUsername.getText()); + appConfig.setValue(AppConfig.PROP_PROXY_NTLM_DOMAIN, textProxyNtlmDomain.getText()); + appConfig.setValue(AppConfig.PROP_PROCESS_KEEP_ACCOUNT_TEAM, buttonKeepAccountTeam.getSelection()); + appConfig.setValue(AppConfig.PROP_UPDATE_WITH_EXTERNALID, buttonUpdateWithExternalId.getSelection()); + appConfig.setValue(AppConfig.PROP_CACHE_DESCRIBE_GLOBAL_RESULTS, buttonCacheDescribeGlobalResults.getSelection()); + appConfig.setValue(AppConfig.PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS, buttonIncludeRTFBinaryDataInQueryResults.getSelection()); + + // Config requires Bulk API AND Bulk V2 API settings enabled to use Bulk V2 features + // This is different from UI. UI shows them as mutually exclusive. + appConfig.setValue(AppConfig.PROP_BULK_API_ENABLED, buttonUseBulkV1Api.getSelection()); + appConfig.setValue(AppConfig.PROP_BULK_API_SERIAL_MODE, buttonBulkApiSerialMode.getSelection()); + appConfig.setValue(AppConfig.PROP_BULK_API_ZIP_CONTENT, buttonBulkApiZipContent.getSelection()); + appConfig.setValue(AppConfig.PROP_BULKV2_API_ENABLED, buttonUseBulkV2Api.getSelection()); + appConfig.setValue(AppConfig.PROP_OAUTH_LOGIN_FROM_BROWSER, buttonLoginFromBrowser.getSelection()); + appConfig.setValue(AppConfig.PROP_WIZARD_CLOSE_ON_FINISH, buttonCloseWizardOnFinish.getSelection()); + appConfig.setValue(AppConfig.PROP_WIZARD_WIDTH, textWizardWidth.getText()); + appConfig.setValue(AppConfig.PROP_WIZARD_HEIGHT, textWizardHeight.getText()); + + appConfig.setValue(AppConfig.PROP_WIZARD_POPULATE_RESULTS_FOLDER_WITH_PREVIOUS_OP_RESULTS_FOLDER, buttonPopulateResultsFolderOnWizardFinishStep.getSelection()); + LoggingUtil.setLoggingLevel(LOGGING_LEVEL[comboLoggingLevelDropdown.getSelectionIndex()]); + String clientIdVal = textProductionPartnerClientID.getText(); + if (clientIdVal != null && !clientIdVal.strip().isEmpty()) { + String propName = AppConfig.OAUTH_PREFIX + AppConfig.SERVER_PROD_ENVIRONMENT_VAL + "." + AppConfig.PARTNER_CLIENTID_LITERAL; + String currentClientIdVal = appConfig.getString(propName); + if (!clientIdVal.equals(currentClientIdVal)) { + appConfig.setValue(propName, clientIdVal); + getController().logout(); + } + } + clientIdVal = textSandboxPartnerClientID.getText(); + if (clientIdVal != null && !clientIdVal.strip().isEmpty()) { + String propName = AppConfig.OAUTH_PREFIX + AppConfig.SERVER_SB_ENVIRONMENT_VAL + "." + AppConfig.PARTNER_CLIENTID_LITERAL; + String currentClientIdVal = appConfig.getString(propName); + if (!clientIdVal.equals(currentClientIdVal)) { + appConfig.setValue(propName, clientIdVal); + getController().logout(); + } + } + clientIdVal = textProductionBulkClientID.getText(); + if (clientIdVal != null && !clientIdVal.strip().isEmpty()) { + String propName = AppConfig.OAUTH_PREFIX + AppConfig.SERVER_PROD_ENVIRONMENT_VAL + "." + AppConfig.BULK_CLIENTID_LITERAL; + String currentClientIdVal = appConfig.getString(propName); + if (!clientIdVal.equals(currentClientIdVal)) { + appConfig.setValue(propName, clientIdVal); + getController().logout(); + } + } + clientIdVal = textSandboxBulkClientID.getText(); + if (clientIdVal != null && !clientIdVal.strip().isEmpty()) { + String propName = AppConfig.OAUTH_PREFIX + AppConfig.SERVER_SB_ENVIRONMENT_VAL + "." + AppConfig.BULK_CLIENTID_LITERAL; + String currentClientIdVal = appConfig.getString(propName); + if (!clientIdVal.equals(currentClientIdVal)) { + appConfig.setValue(propName, clientIdVal); + getController().logout(); + } + } + getController().saveConfig(); + getController().getLoaderWindow().refresh(); shell.close(); } }); @@ -686,7 +1124,6 @@ public void widgetSelected(SelectionEvent event) { cancel.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - input = null; shell.close(); } }); @@ -701,35 +1138,139 @@ public void widgetSelected(SelectionEvent event) { // user can type input and press Enter // to dismiss shell.setDefaultButton(ok); + + empty = new Label(buttonComp, SWT.NONE); + data = new GridData(); + data.horizontalSpan = 2; + empty.setLayoutData(data); // Set the child as the scrolled content of the ScrolledComposite sc.setContent(container); // Set the minimum size - sc.setMinSize(768, 1024); + sc.addControlListener(new ControlAdapter() { + public void controlResized(ControlEvent e) { + Rectangle r = sc.getClientArea(); + sc.setMinSize(container.computeSize(r.width, SWT.DEFAULT)); + } + }); + sc.setAlwaysShowScrollBars(true); // Expand both horizontally and vertically sc.setExpandHorizontal(true); sc.setExpandVertical(true); + shell.redraw(); + } + + private String getImportBatchLimitsURL() { + if (this.useBulkAPI || this.useBulkV2API) { + return "https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_bulkapi.htm"; + } + return "https://developer.salesforce.com/docs/atlas.en-us.salesforce_app_limits_cheatsheet.meta/salesforce_app_limits_cheatsheet/salesforce_app_limits_platform_apicalls.htm"; } - private Text createTextInput(Composite parent, String labelKey, String configKey, String defaultValue, int widthHint) { - // TODO: use this method to create all text inputs - createLabel(parent, labelKey); - final Text t = new Text(parent, SWT.BORDER); + private Text createTimezoneTextInput(Composite parent, String configKey, String defaultValue, int widthHint) { + createLink(parent, null, null, configKey); + + Composite timezoneComp = new Composite(parent, SWT.RIGHT); + GridData data = new GridData(GridData.FILL_BOTH); + timezoneComp.setLayoutData(data); + GridLayout layout = new GridLayout(2, false); + layout.verticalSpacing = 10; + timezoneComp.setLayout(layout); + + final Text t = new Text(timezoneComp, SWT.BORDER); final GridData gd = new GridData(); if (widthHint > 0) gd.widthHint = widthHint; t.setLayoutData(gd); - String val = controller.getConfig().getString(configKey); + String val = getController().getAppConfig().getString(configKey); if ("".equals(val) && defaultValue != null) val = defaultValue; t.setText(String.valueOf(val)); + + buttonLocalSystemTimezone = new Button(timezoneComp, SWT.PUSH | SWT.FLAT); + buttonLocalSystemTimezone.setText(Labels.getString("AdvancedSettingsDialog.uiLabel.setClientSystemTimezone")); //$NON-NLS-1$ + buttonLocalSystemTimezone.setToolTipText(Labels.getString("AdvancedSettingsDialog.uiTooltip.TooltipSetClientSystemTimezone")); + buttonLocalSystemTimezone.addSelectionListener(new SelectionAdapter() { + public void widgetSelected(SelectionEvent event) { + t.setText(TimeZone.getDefault().getID()); + } + }); return t; } - - - private void createLabel(Composite parent, String labelKey) { - Label l = new Label(parent, SWT.RIGHT); - l.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); - l.setText(Labels.getString(labelKey)); + + private Text createLabel(Composite parent, String labelKey, String[] args, String propertyName) { + Text l = new Text(parent, SWT.RIGHT | SWT.WRAP | SWT.READ_ONLY); + GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END); + data.grabExcessHorizontalSpace = true; + l.setLayoutData(data); + if (labelKey == null) { + if (propertyName != null) { + l.setText(Labels.getFormattedString("AdvancedSettingsDialog.uiLabel." + propertyName, args)); + } + } else { + l.setText(Labels.getFormattedString("AdvancedSettingsDialog.uiLabel." + labelKey, args)); + } + String tooltipText = getTooltipText(labelKey, propertyName); + if (tooltipText != null) { + l.setToolTipText(tooltipText); + } + l.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + URLUtil.openURL(e.text); + } + }); + return l; + } + + private Link createLink(Composite parent, String labelKey, String[] args, String propertyName) { + Link l = new Link(parent, SWT.RIGHT | SWT.MULTI); + GridData data = new GridData(GridData.HORIZONTAL_ALIGN_END); + data.grabExcessHorizontalSpace = true; + l.setLayoutData(data); + + String labelLookupKey = labelKey == null ? propertyName : labelKey; + l.setText(Labels.getFormattedString("AdvancedSettingsDialog.uiLabel." + labelLookupKey, args)); + String tooltipText = getTooltipText(labelKey, propertyName); + if (tooltipText != null) { + l.setToolTipText(tooltipText); + } + l.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + URLUtil.openURL(e.text); + } + }); + return l; + } + + private String getTooltipText(String labelLookupKey, String propertyName) { + String tooltipText = null; + + if (labelLookupKey != null) { + tooltipText = Labels.getString("AdvancedSettingsDialog.uiTooltip." + labelLookupKey); + } else { // both labelLookupKey and propertyName can't be null + tooltipText = Labels.getString("AdvancedSettingsDialog.uiTooltip." + propertyName); + } + if (tooltipText != null && tooltipText.startsWith("!") && tooltipText.endsWith("!")) { + tooltipText = null; + } + if (propertyName != null) { + String[] propArg = {propertyName}; + try { + if (tooltipText == null) { + tooltipText = Labels.getFormattedString("AdvancedSettingsDialog.TooltipPropertyName", propArg); + } else { + tooltipText += "\n\n"; + tooltipText += Labels.getFormattedString("AdvancedSettingsDialog.TooltipPropertyName", propArg); + } + } catch (java.util.MissingResourceException e) { + // do nothing + } + } + if (tooltipText == null) { + tooltipText = ""; + } + return tooltipText; } } diff --git a/src/main/java/com/salesforce/dataloader/ui/AuthenticationRunner.java b/src/main/java/com/salesforce/dataloader/ui/AuthenticationRunner.java index d93693db2..51c9cf403 100644 --- a/src/main/java/com/salesforce/dataloader/ui/AuthenticationRunner.java +++ b/src/main/java/com/salesforce/dataloader/ui/AuthenticationRunner.java @@ -27,107 +27,178 @@ package com.salesforce.dataloader.ui; import com.salesforce.dataloader.client.DefaultSimplePost; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.model.LoginCriteria; import com.salesforce.dataloader.util.ExceptionUtil; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.soap.partner.QueryResult; import com.sforce.soap.partner.fault.LoginFault; import com.sforce.soap.partner.fault.UnexpectedErrorFault; -import org.apache.log4j.Logger; +import com.sforce.soap.partner.sobject.SObject; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.bind.XmlObject; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Level; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; +import java.util.Iterator; import java.util.function.Consumer; /** * AuthenticationRunner is the UI orchestration of logging in. */ public class AuthenticationRunner { - private static Logger logger = Logger.getLogger(AuthenticationRunner.class); + private static Logger logger = DLLogManager.getLogger(AuthenticationRunner.class); - private final Config config; + private final AppConfig appConfig; private final Controller controller; - private final Consumer complete; private final String nestedException = "nested exception is:"; private final Shell shell; - private Consumer messenger; + private Consumer authStatusChangeConsumer; private LoginCriteria criteria; - public AuthenticationRunner(Shell shell, Config config, Controller controller, Consumer complete) { + public AuthenticationRunner(Shell shell, AppConfig appConfig, Controller controller) { this.shell = shell; - this.config = config; + this.appConfig = appConfig; this.controller = controller; - this.complete = complete; } - public Config getConfig() { - return config; + public AppConfig getConfig() { + return appConfig; } public void login(LoginCriteria criteria, Consumer messenger) { - this.messenger = messenger; + this.authStatusChangeConsumer = messenger; this.criteria = criteria; - criteria.updateConfig(config); + criteria.updateConfig(appConfig); BusyIndicator.showWhile(Display.getDefault(), new Thread(this::loginAsync)); } private void loginAsync(){ try { - messenger.accept(Labels.getString("SettingsPage.verifyingLogin")); - - if (criteria.getMode() == LoginCriteria.Default){ - - boolean hasSecret = !config.getString(Config.OAUTH_CLIENTSECRET).trim().equals(""); - OAuthFlow flow = hasSecret ? new OAuthSecretFlow(shell, config) : new OAuthTokenFlow(shell, config); - if (!flow.open()){ - String message = Labels.getString("SettingsPage.invalidLogin"); - if (flow.getStatusCode() == DefaultSimplePost.PROXY_AUTHENTICATION_REQUIRED) { - message = Labels.getFormattedString("SettingsPage.proxyError", flow.getReasonPhrase()); + authStatusChangeConsumer.accept(Labels.getString("LoginPage.verifyingLogin")); + logger.info(Labels.getString("LoginPage.verifyingLogin")); + if (criteria.getMode() == LoginCriteria.OAuthLogin){ + if (appConfig.getBoolean(AppConfig.PROP_OAUTH_LOGIN_FROM_BROWSER)) { + OAuthLoginFromBrowserFlow flow = new OAuthLoginFromBrowserFlow(shell, appConfig); + if (!flow.open()) { + String message = Labels.getString("LoginPage.invalidLoginOAuthBrowser"); + authStatusChangeConsumer.accept(message); + return; + } + } else { // OAuth login from Data Loader app + boolean hasSecret = !appConfig.getOAuthClientSecretForCurrentEnv().trim().equals(""); + OAuthFlow flow = hasSecret ? new OAuthSecretFlow(shell, appConfig) : new OAuthTokenFlow(shell, appConfig); + if (!flow.open()) { + String message = Labels.getString("LoginPage.invalidLoginOAuth"); + if (flow.getStatusCode() == DefaultSimplePost.PROXY_AUTHENTICATION_REQUIRED) { + message = Labels.getFormattedString("LoginPage.proxyError", flow.getReasonPhrase()); + } + + if (flow.getReasonPhrase() == null) { + logger.info("OAuth login dialog closed without logging in"); + } else { + logger.info("Login failed:" + flow.getReasonPhrase()); + } + authStatusChangeConsumer.accept(message); + return; } - - logger.info("Login failed:" + flow.getReasonPhrase()); - messenger.accept(message); - complete.accept(false); - return; } - } - if (controller.login() && controller.setEntityDescribes()) { - messenger.accept(Labels.getString("SettingsPage.loginSuccessful")); + } + } catch (Throwable e) { + handleError(e, e.getMessage()); + } + // Either OAuth login is successful or + // need to perform username and password or session token based auth + try { + if (controller.login() && controller.getEntityDescribes() != null) { controller.saveConfig(); - complete.accept(true); + controller.updateLoaderWindowTitleAndCacheUserInfoForTheSession(); + PartnerConnection conn = controller.getPartnerClient().getConnection(); + logger.debug("org_id = " + conn.getUserInfo().getOrganizationId()); + logger.debug("user_id = " + conn.getUserInfo().getUserId()); + if (logger.getLevel() == Level.DEBUG) { + // avoid making a remote API call to the server unless log level is DEBUG + logger.debug(getSoqlResultsAsString( + "\nConnected App Information: ", + "SELECT Name, id FROM ConnectedApplication WHERE name like 'Dataloader%'" + , conn)); + logger.debug(getSoqlResultsAsString( + "\nOrg Instance Information:", + "SELECT InstanceName FROM Organization" + , conn)); + } + authStatusChangeConsumer.accept(Labels.getString("LoginPage.loginSuccessful")); } else { - messenger.accept(Labels.getString("SettingsPage.invalidLogin")); - complete.accept(false); + authStatusChangeConsumer.accept(Labels.getString("LoginPage.invalidLoginUsernamePassword")); } - } catch (LoginFault lf ) { - messenger.accept(Labels.getString("SettingsPage.invalidLogin")); - complete.accept(false); + } catch (LoginFault lf) { + handleError(lf, Labels.getString("LoginPage.invalidLoginUsernamePassword")); } catch (UnexpectedErrorFault e) { handleError(e, e.getExceptionMessage()); - } catch (Throwable e) { + } catch (ConnectionException e) { + // TODO Auto-generated catch block handleError(e, e.getMessage()); + } + + } + + private String getSoqlResultsAsString(String prefix, String soql, PartnerConnection conn) throws ConnectionException { + QueryResult result = conn.query(soql); + final SObject[] sfdcResults = result.getRecords(); + String debugInfo = prefix; + if (sfdcResults != null) { + for (SObject sobj : sfdcResults) { + Iterator fields = sobj.getChildren(); + if (fields == null) continue; + String fieldsStr = " "; + boolean isIdInFields = false; + while (fields.hasNext()) { + XmlObject field = fields.next(); + if ("type".equalsIgnoreCase(field.getName().getLocalPart())) { + continue; + } + if (field.getValue() == null || field.getValue().toString().isBlank()) { + continue; + } + if ("id".equalsIgnoreCase(field.getName().getLocalPart())) { + if (isIdInFields) { + continue; + } + isIdInFields = true; + } + fieldsStr += field.getName().getLocalPart() + " = " + field.getValue() + " "; + } + debugInfo += "\n" + fieldsStr; + } } + return debugInfo; } private void handleError(Throwable e, String message) { if (message == null || message.length() < 1) { - messenger.accept(Labels.getString("SettingsPage.invalidLogin")); + authStatusChangeConsumer.accept(Labels.getString("LoginPage.invalidLogin")); + logger.error(Labels.getString("LoginPage.invalidLogin")); } else { int x = message.indexOf(nestedException); if (x >= 0) { x += nestedException.length(); message = message.substring(x); } - messenger.accept(message.replace('\n', ' ').trim()); + authStatusChangeConsumer.accept(message.replace('\n', ' ').trim()); + logger.error(message); } - complete.accept(false); - logger.error(message); - logger.error("\n" + ExceptionUtil.getStackTraceString(e)); + logger.debug("\n" + ExceptionUtil.getStackTraceString(e)); } } diff --git a/src/main/java/com/salesforce/dataloader/ui/BaseDialog.java b/src/main/java/com/salesforce/dataloader/ui/BaseDialog.java new file mode 100644 index 000000000..ac66b6b29 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/BaseDialog.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Dialog; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; + +public abstract class BaseDialog extends Dialog { + + private Controller controller; + private String message; + protected Logger logger; + protected boolean success = true; + + /** + * InputDialog constructor + * + * @param parent + * the parent + */ + protected BaseDialog(Shell parent, Controller controller) { + super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); + this.controller = controller; + this.logger = DLLogManager.getLogger(this.getClass()); + setText(Labels.getString(this.getClass().getSimpleName() + ".title")); //$NON-NLS-1$ + setMessage(Labels.getString(this.getClass().getSimpleName() + ".message")); //$NON-NLS-1$ + } + + protected Shell openAndGetShell() { + final Shell shell = new Shell(getParent(), getStyle()); + shell.setText(getText()); + shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ + createContents(shell); + shell.pack(); + shell.open(); + setShellBounds(shell); + return shell; + } + + protected abstract void createContents(Shell shell); + + /** + * Gets the message + * + * @return String + */ + public String getMessage() { + return message; + } + + /** + * Sets the message + * + * @param message + * the new message + */ + public void setMessage(String message) { + this.message = message; + } + + protected Controller getController() { + return this.controller; + } + + /** + * Opens the dialog and returns the input + * + * @return String + */ + public boolean open() { + // Create the dialog window + Shell shell = openAndGetShell(); + Display display = shell.getDisplay(); + doWork(shell); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + } + return success; + } + + protected void setShellBounds(Shell dialogShell) { + Point parentLocation = getParent().getLocation(); + dialogShell.setLocation(parentLocation.x + AppConfig.DIALOG_X_OFFSET, + parentLocation.y + AppConfig.DIALOG_Y_OFFSET); + } + + private void doWork(Shell shell) { + Display display = shell.getDisplay(); + BusyIndicator.showWhile(display, new Thread() { + @Override + public void run() { + processingWithBusyIndicator(shell); + } + }); + } + + protected void processingWithBusyIndicator(Shell shell) { + // no op + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/BaseWizard.java b/src/main/java/com/salesforce/dataloader/ui/BaseWizard.java index abc9c7627..607b95d38 100644 --- a/src/main/java/com/salesforce/dataloader/ui/BaseWizard.java +++ b/src/main/java/com/salesforce/dataloader/ui/BaseWizard.java @@ -27,10 +27,9 @@ import org.eclipse.jface.wizard.Wizard; import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.graphics.Image; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; public abstract class BaseWizard extends Wizard { @@ -41,19 +40,19 @@ public abstract class BaseWizard extends Wizard { protected BaseWizard(Controller ctl, OperationInfo info) { this.controller = ctl; - getConfig().setValue(Config.OPERATION, info.name()); + getConfig().setValue(AppConfig.PROP_OPERATION, info.name()); this.finishPage = setupPages(); // Set the dialog window title - setWindowTitle(getLabel("title")); + setWindowTitle(getLabel("windowTitle")); } - protected abstract SettingsPage createSettingsPage(); + protected abstract LoginPage createLoginPage(); protected abstract WizardPage setPages(); private WizardPage setupPages() { - if (SettingsPage.isNeeded(getController())) addPage(createSettingsPage()); + if (LoginPage.isNeeded(getController())) addPage(createLoginPage()); return setPages(); } @@ -62,8 +61,8 @@ protected Controller getController() { return this.controller; } - protected Config getConfig() { - return getController().getConfig(); + protected AppConfig getConfig() { + return getController().getAppConfig(); } protected String getLabel(String name) { @@ -77,5 +76,9 @@ protected String getLabelSection() { protected WizardPage getFinishPage() { return finishPage; } + + protected boolean closeWizardPagePostSuccessfulFinish() { + return getConfig().getBoolean(AppConfig.PROP_WIZARD_CLOSE_ON_FINISH); + } } diff --git a/src/main/java/com/salesforce/dataloader/ui/CSVChooserDialog.java b/src/main/java/com/salesforce/dataloader/ui/CSVChooserDialog.java index 8e7b2875e..d39720a7a 100644 --- a/src/main/java/com/salesforce/dataloader/ui/CSVChooserDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/CSVChooserDialog.java @@ -26,7 +26,7 @@ package com.salesforce.dataloader.ui; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; @@ -41,7 +41,6 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Label; @@ -49,10 +48,7 @@ import org.eclipse.swt.widgets.Text; import org.springframework.util.StringUtils; -public class CSVChooserDialog extends Dialog { - private String message; - private String input; - private Controller controller; +public class CSVChooserDialog extends WizardDialog { private Text textRows; private Button buttonSelect; private Button buttonSuccess; @@ -66,84 +62,7 @@ public class CSVChooserDialog extends Dialog { */ public CSVChooserDialog(Shell parent, Controller controller) { // Pass the default styles here - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - this.controller = controller; - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public CSVChooserDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - setText(Labels.getString("CSVChooser.title")); //$NON-NLS-1$ - setMessage(Labels.getString("CSVChooser.message")); //$NON-NLS-1$ - } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; - } - - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Gets the input - * - * @return String - */ - public String getInput() { - return input; - } - - /** - * Sets the input - * - * @param input - * the new input - */ - public void setInput(String input) { - this.input = input; - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public String open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the entered value, or null - return input; + super(parent, controller); } /** @@ -152,8 +71,7 @@ public String open() { * @param shell * the dialog window */ - private void createContents(final Shell shell) { - + protected void createContents(final Shell shell) { GridData data; GridLayout layout = new GridLayout(1, false); layout.verticalSpacing = 10; @@ -181,7 +99,7 @@ private void createContents(final Shell shell) { // Show the message Label label = new Label(topComp, SWT.NONE); - label.setText(message); + label.setText(getMessage()); data = new GridData(GridData.FILL_HORIZONTAL); data.heightHint = 30; data.widthHint = 370; @@ -239,9 +157,8 @@ public void widgetSelected(SelectionEvent event) { FileDialog dlg = new FileDialog(shell, SWT.OPEN); String fn = dlg.open(); if (fn != null) { - openViewer(fn, true); + openViewer(fn, false, false); } - } }); @@ -255,9 +172,9 @@ public void widgetSelected(SelectionEvent event) { buttonSuccess.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - String successFilePath = controller.getConfig().getString(Config.OUTPUT_SUCCESS); + String successFilePath = getController().getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); if(StringUtils.hasText(successFilePath)) { - openViewer(successFilePath, false); + openViewer(successFilePath, true, false); } else { UIUtils.infoMessageBox(shell, Messages.getString("CSVChooser.noSucessOrErrorFile")); } @@ -274,9 +191,9 @@ public void widgetSelected(SelectionEvent event) { buttonError.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - String errorFilePath = controller.getConfig().getString(Config.OUTPUT_ERROR); + String errorFilePath = getController().getAppConfig().getString(AppConfig.PROP_OUTPUT_ERROR); if(StringUtils.hasText(errorFilePath)) { - openViewer(errorFilePath, false); + openViewer(errorFilePath, true, false); } else { UIUtils.infoMessageBox(shell, Messages.getString("CSVChooser.noSucessOrErrorFile")); } @@ -305,8 +222,6 @@ public void widgetSelected(SelectionEvent event) { ok.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - - input = "OK"; //$NON-NLS-1$ shell.close(); } }); @@ -321,11 +236,11 @@ public void widgetSelected(SelectionEvent event) { shell.setDefaultButton(ok); } - private void openViewer(String filename, boolean useCustomSplitter) { - CSVViewerDialog dlg = new CSVViewerDialog(getParent(), controller); + private void openViewer(String filename, boolean ignoreDelimiterConfig, boolean isQueryOperationResult) { + // + CSVViewerDialog dlg = new CSVViewerDialog(getParent(), getController(), ignoreDelimiterConfig, isQueryOperationResult); dlg.setNumberOfRows(Integer.parseInt(textRows.getText())); dlg.setFileName(filename); - dlg.setUseCustomSplitter(useCustomSplitter); try { dlg.open(); } catch (DataAccessObjectInitializationException e) { diff --git a/src/main/java/com/salesforce/dataloader/ui/CSVViewerDialog.java b/src/main/java/com/salesforce/dataloader/ui/CSVViewerDialog.java index 4d552ddf4..f946e64a3 100644 --- a/src/main/java/com/salesforce/dataloader/ui/CSVViewerDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/CSVViewerDialog.java @@ -26,13 +26,15 @@ package com.salesforce.dataloader.ui; -import java.io.IOException; +import java.io.File; import java.util.ArrayList; +import java.util.Arrays; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -49,15 +51,16 @@ import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; +import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import com.salesforce.dataloader.ui.csvviewer.CSVContentProvider; import com.salesforce.dataloader.ui.csvviewer.CSVLabelProvider; +import com.salesforce.dataloader.util.AppUtil; import com.salesforce.dataloader.util.DAORowUtil; -import com.salesforce.dataloader.util.StreamGobbler; /** * This class creates the mapping dialog @@ -65,20 +68,16 @@ public class CSVViewerDialog extends Dialog { private String input; - private Logger logger = Logger.getLogger(CSVViewerDialog.class); + private Logger logger = DLLogManager.getLogger(CSVViewerDialog.class); private String filename; - private boolean useCustomSplitter = false; + private final boolean ignoreDelimiterConfig; + private final boolean isQueryOperationResult; private int numberOfRows; //the two tableViewers private TableViewer csvTblViewer; private final Controller controller; - public void setUseCustomSplitter(boolean useCustomSplitter) - { - this.useCustomSplitter = useCustomSplitter; - } - public void setFileName(String filename) { this.filename = filename; } @@ -87,25 +86,18 @@ public void setNumberOfRows(int rows) { numberOfRows = rows; } - /** - * @param parent - */ - public CSVViewerDialog(Shell parent, Controller controller) { - // Pass the default styles here - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.MAX | SWT.MIN, controller); - } - /** * @param parent * the parent * @param style * the style */ - public CSVViewerDialog(Shell parent, int style, Controller controller) { - // Let users override the default styles - super(parent, style); + public CSVViewerDialog(Shell parent, Controller controller, boolean ignoreDelimiterConfig, boolean isQueryOperationResult) { + super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.MAX | SWT.MIN); setText(Labels.getString("CSVViewer.title")); //$NON-NLS-1$ this.controller = controller; + this.ignoreDelimiterConfig = ignoreDelimiterConfig; + this.isQueryOperationResult = isQueryOperationResult; } /** @@ -196,21 +188,9 @@ public void widgetSelected(SelectionEvent e) { Thread runner = new Thread() { @Override public void run() { - int exitVal = 0; - try { - String[] cmd = { "cmd.exe", "/c", "\"" + filename + "\"" }; //$NON-NLS-1$ //$NON-NLS-2$ - Process proc = Runtime.getRuntime().exec(cmd); - StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR"); //$NON-NLS-1$ - StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT"); //$NON-NLS-1$ - errorGobbler.start(); - outputGobbler.start(); - exitVal = proc.waitFor(); - } catch (IOException iox) { - logger.error(Labels.getString("CSVViewerDialog.errorExternal"), iox); //$NON-NLS-1$ - } catch (InterruptedException ie) { - logger.error(Labels.getString("CSVViewerDialog.errorExternal"), ie); //$NON-NLS-1$ - } - + String[] cmd = { "cmd.exe", "/c", "\"" + filename + "\"" }; //$NON-NLS-1$ //$NON-NLS-2$ + + int exitVal = AppUtil.exec(Arrays.asList(cmd), Labels.getString("CSVViewerDialog.errorExternal")); if (exitVal != 0) { logger.error(Labels.getString("CSVViewerDialog.errorProcessExit") + exitVal); //$NON-NLS-1$ } @@ -248,13 +228,22 @@ public void widgetSelected(SelectionEvent event) { private void initializeCSVViewer(Shell shell) throws DataAccessObjectInitializationException { GridData data; - - CSVFileReader csvReader = new CSVFileReader(filename, controller, useCustomSplitter); - + + // use delimiter settings for load operations by specifying 'false' for isQueryOperationResult param + CSVFileReader csvReader = new CSVFileReader(new File(filename), + controller.getAppConfig(), ignoreDelimiterConfig, isQueryOperationResult); try { - csvReader.open(); + List header = new ArrayList(); + header.add(""); + try { + csvReader.open(); + header = csvReader.getColumnNames(); + } catch (DataAccessObjectInitializationException ex) { + if (!Messages.getString("CSVFileDAO.errorHeaderRow").equals(ex.getMessage())) { + throw ex; + } + } - List header = csvReader.getColumnNames(); //sforce field table viewer csvTblViewer = new TableViewer(shell, SWT.FULL_SELECTION); @@ -300,25 +289,25 @@ private void initializeCSVViewer(Shell shell) throws DataAccessObjectInitializat */ private void updateCSVTable(CSVFileReader csvReader) { - List> rowList = new LinkedList>(); + List> rowList = new ArrayList>(); for (int i = 0; i < numberOfRows; i++) { - Row rowMap; + TableRow row; try { - rowMap = csvReader.readRow(); + row = csvReader.readTableRow(); } catch (DataAccessObjectException e) { break; } - if (!DAORowUtil.isValidRow(rowMap)) { + if (!DAORowUtil.isValidTableRow(row)) { break; } List columns = csvReader.getColumnNames(); - List row = new ArrayList(); - row.add(0, String.valueOf(i + 1)); + List listOfRowCells = new ArrayList(); + listOfRowCells.add(0, String.valueOf(i + 1)); for(String column : columns) { - row.add(rowMap.get(column)); + listOfRowCells.add(row.get(column)); } - rowList.add(row); + rowList.add(listOfRowCells); } csvReader.close(); csvTblViewer.setInput(rowList); diff --git a/src/main/java/com/salesforce/dataloader/ui/ChooseLookupFieldForRelationshipPage.java b/src/main/java/com/salesforce/dataloader/ui/ChooseLookupFieldForRelationshipPage.java new file mode 100644 index 000000000..e052ae745 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/ChooseLookupFieldForRelationshipPage.java @@ -0,0 +1,390 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import java.util.*; +import java.util.List; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.layout.*; +import org.eclipse.swt.widgets.*; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.client.DescribeRefObject; +import com.salesforce.dataloader.client.ReferenceEntitiesDescribeMap; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dyna.ParentIdLookupFieldFormatter; +import com.salesforce.dataloader.dyna.ParentSObjectFormatter; +import com.salesforce.dataloader.exception.RelationshipFormatException; +import com.sforce.soap.partner.Field; +import com.sforce.soap.partner.FieldType; + +/** + * Page for selecting external id fields for the foreign key objects + * + * @author Alex Warshavsky + * @since 8.0 + */ +public class ChooseLookupFieldForRelationshipPage extends LoadPage { + + private final Map parentLookupFieldSelectionMap = new HashMap(); + private final Map parentSObjectMap = new HashMap(); + private Composite containerComp; + private ScrolledComposite scrollComp; + private ReferenceEntitiesDescribeMap parentSObjectDescribeMap; + private boolean hasParentEntitiesWithIdLookupField = false; + + + public ChooseLookupFieldForRelationshipPage(Controller controller) { + super("ChooseLookupFieldForRelationshipPage", controller); //$NON-NLS-1$ + // Mark this page as completed as the selected sObject may not have any foreign key + setPageComplete(); + } + + @Override + public void createControl(Composite parent) { + containerComp = new Composite(parent, SWT.NONE); + containerComp.setLayout(new FillLayout()); + + setControl(containerComp); + setupPage(); + } + + private void createFkExtIdUi() { + getShell().setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ + + if(scrollComp != null) { + scrollComp.dispose(); + } + scrollComp = new ScrolledComposite(containerComp, SWT.V_SCROLL); + scrollComp.setExpandHorizontal(true); + scrollComp.setExpandVertical(true); + Composite comp = new Composite(scrollComp, SWT.NONE); + scrollComp.setContent(comp); + + GridLayout gridLayout = new GridLayout(5, false); + gridLayout.horizontalSpacing = 10; + gridLayout.marginHeight = 20; + gridLayout.verticalSpacing = 7; + comp.setLayout(gridLayout); + + scrollComp.setMinSize(comp.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + + // set scrolling specific to data + ScrollBar scrollBar = scrollComp.getVerticalBar(); + scrollBar.setIncrement(20); + scrollBar.setPageIncrement(20 * 5); + + parentLookupFieldSelectionMap.clear(); + parentSObjectMap.clear(); + this.hasParentEntitiesWithIdLookupField = false; + if(parentSObjectDescribeMap != null) { + Label relNameHeader = new Label(comp, SWT.RIGHT); + relNameHeader.setText(Labels.getString(getClass().getSimpleName() + ".relationshipHeader")); + relNameHeader.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); + Font f = relNameHeader.getFont(); + FontData[] farr = f.getFontData(); + FontData fd = farr[0]; + fd.setStyle(SWT.BOLD); + relNameHeader.setFont(new Font(Display.getCurrent(), fd)); + + Label parentObjectSeparator = new Label(comp, SWT.CENTER); + parentObjectSeparator.setText(ParentSObjectFormatter.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR); + parentObjectSeparator.setFont(new Font(Display.getCurrent(), fd)); + + Label parentObjectNameHeader = new Label(comp, SWT.RIGHT); + parentObjectNameHeader.setText(Labels.getString( + getClass().getSimpleName() + ".parentObjectHeader")); + parentObjectNameHeader.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING)); + parentObjectNameHeader.setFont(new Font(Display.getCurrent(), fd)); + + Label parentLookupFieldSeparator = new Label(comp, SWT.CENTER); + parentLookupFieldSeparator.setText(ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR); + parentLookupFieldSeparator.setFont(new Font(Display.getCurrent(), fd)); + + Label idLookupFieldNameHeader = new Label(comp, SWT.RIGHT); + idLookupFieldNameHeader.setText(Labels.getString( + getClass().getSimpleName() + ".parentLookupFieldHeader")); + idLookupFieldNameHeader.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING)); + idLookupFieldNameHeader.setFont(new Font(Display.getCurrent(), fd)); + + List sortedRelationshipList = new ArrayList<>(parentSObjectDescribeMap.keySet()); + Collections.sort(sortedRelationshipList); + for(String relationshipName : sortedRelationshipList) { + OperationInfo operation = controller.getAppConfig().getOperationInfo(); + Field childField = parentSObjectDescribeMap.getParentSObject(relationshipName).getChildField(); + boolean isCreateableOrUpdateable = true; + if (childField != null) { + switch (operation) { + case insert: + if (!childField.isCreateable()) { + isCreateableOrUpdateable = false; + } + break; + case update: + if (!childField.isUpdateable()) { + isCreateableOrUpdateable = false; + } + break; + default: + break; + } + } + if (isCreateableOrUpdateable) { + createUIToMapParentLookupFieldChoices(comp, relationshipName); + hasParentEntitiesWithIdLookupField = true; + } + } + } + scrollComp.setMinSize(comp.computeSize(SWT.DEFAULT, SWT.DEFAULT)); + containerComp.layout(); + } + + /** + * Create UI components for one object: label and a combo box with external id fields + * @param comp + * @param relationshipName + */ + private void createUIToMapParentLookupFieldChoices(Composite comp, String relationshipName) { + ParentSObjectFormatter relField; + try { + relField = new ParentSObjectFormatter(relationshipName); + } catch (RelationshipFormatException e) { + logger.error(e.getMessage()); + return; + } + + // Add parent dropdown + if (relField.getParentObjectName() == null) { + // shouldn't happen + return; + } + + Combo parentSObjectCombo = this.parentSObjectMap.get(relField.getRelationshipName()); + if (parentSObjectCombo == null) { + // first parent object + Label relName = new Label(comp, SWT.RIGHT); + relName.setText(relField.getRelationshipName()); + relName.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); + + Label parentObjectSeparator = new Label(comp, SWT.CENTER); + parentObjectSeparator.setText(ParentSObjectFormatter.NEW_FORMAT_RELATIONSHIP_NAME_SEPARATOR_CHAR); + + parentSObjectCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY); + GridData parentData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + parentData.widthHint = 150; + parentSObjectCombo.setLayoutData(parentData); + parentSObjectCombo.add(relField.getParentObjectName()); + parentSObjectCombo.select(0); + parentSObjectCombo.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + String parentName = ((Combo)event.widget).getText(); + Combo parentLookupIdFieldCombo = parentLookupFieldSelectionMap.get(relField.toString()); + parentLookupIdFieldCombo.removeAll(); + ParentSObjectFormatter selectedRelationship; + try { + selectedRelationship = new ParentSObjectFormatter(parentName, relField.getRelationshipName()); + } catch (RelationshipFormatException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + return; + } + populateParentLookupFieldCombo(parentLookupIdFieldCombo, selectedRelationship); + } + }); + parentSObjectMap.put(relField.getRelationshipName(), parentSObjectCombo); + Label parentLookupFieldSeparator = new Label(comp, SWT.CENTER); + parentLookupFieldSeparator.setText(ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR); + // Add the ext id dropdown + Combo parentLookupFieldCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY); + + // The width comes out based on number of pixels, not characters + GridData extIdData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); + extIdData.widthHint = 150; + parentLookupFieldCombo.setLayoutData(extIdData); + populateParentLookupFieldCombo(parentLookupFieldCombo, relField); + parentLookupFieldSelectionMap.put(relField.toString(), parentLookupFieldCombo); + } else { + parentSObjectCombo.add(relField.getParentObjectName()); + } + } + + private void populateParentLookupFieldCombo(Combo extIdCombo, ParentSObjectFormatter relField) { + DescribeRefObject extIdInfo = parentSObjectDescribeMap.getParentSObject(relField.toString()); + + // get object's ext id info & set combo box to list of external id fields + // set the objects reference information + List fieldList = new ArrayList(extIdInfo.getParentObjectFieldMap().keySet()); + // add default selection "not selected" to the list to allow users to go back to it + String defaultListItemStr = Labels.getString(getClass().getSimpleName() + ".defaultComboText"); + fieldList.add(defaultListItemStr); + UIUtils.setComboItems(extIdCombo, fieldList, defaultListItemStr); + } + + /** + * Returns the next page, sets the external id + * + * @return IWizardPage + */ + @Override + public MappingPage getNextPage() { + // save data from this page and remember field selections for mapping + Map relatedFields = saveParentIdLookupFieldData(); + + MappingPage nextPage = (MappingPage)getWizard().getPage(MappingPage.class.getSimpleName()); //$NON-NLS-1$ + nextPage.setRelatedFields(relatedFields); + nextPage.setupPage(); + return nextPage; + } + + /** + * Save the data from this page in the config and return the selected external id field info + * @return selected objects external id field info + */ + private Map saveParentIdLookupFieldData() { + Map relatedFields = new HashMap(); + + // foreign key references (if any set) + for(String parentAndRelNameInCombo : parentLookupFieldSelectionMap.keySet()) { + Combo combo = parentLookupFieldSelectionMap.get(parentAndRelNameInCombo); + String lookupFieldInParent = combo.getText(); + ParentIdLookupFieldFormatter relationshipField = getSelectedParentSObjectForLookupField(parentAndRelNameInCombo, lookupFieldInParent); + // make sure that the item selection has occurred and that the default text is not displayed anymore + if(relationshipField != null + && !relationshipField.getParentFieldName().equalsIgnoreCase(Labels.getString(getClass().getSimpleName() + ".defaultComboText"))) { + DescribeRefObject refDescribe = parentSObjectDescribeMap.getParentSObject(relationshipField.getParent().toString()); + Field relatedField = new Field(); + Field parentField = refDescribe.getParentObjectFieldMap().get(lookupFieldInParent); + Field childField = refDescribe.getChildField(); + relatedField.setName(relationshipField.toString()); + String childFieldLabel = childField.getLabel(); + String[] childFieldLabelParts = childFieldLabel.split(" \\(.+\\)$"); + relatedField.setLabel(childFieldLabelParts[0] + " (" + parentField.getLabel() + ")"); + relatedField.setCreateable(childField.isCreateable()); + relatedField.setUpdateable(childField.isUpdateable()); + relatedField.setType(FieldType.reference); + String[] refToArray = new String[1]; + refToArray[0] = refDescribe.getParentObjectName(); + relatedField.setReferenceTo(refToArray); + relatedFields.put(relationshipField.toString(),relatedField); + } + } + + return relatedFields; + } + + private ParentIdLookupFieldFormatter getSelectedParentSObjectForLookupField(String parentAndRelNameInCombo, String selectedIdLookupField) { + if(selectedIdLookupField != null && selectedIdLookupField.length() > 0 + && ! selectedIdLookupField.equals(Labels.getString("ForeignKeyExternalIdPage.defaultComboText"))) { + ParentSObjectFormatter selectedParentSObjectField; + try { + selectedParentSObjectField = new ParentSObjectFormatter(parentAndRelNameInCombo); + } catch (RelationshipFormatException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + return null; + } + Combo parentSObjectCombo = this.parentSObjectMap.get(selectedParentSObjectField.getRelationshipName()); + if (parentSObjectCombo == null) { + return null; + } + String actualParentSObjectName = parentSObjectCombo.getText(); + ParentIdLookupFieldFormatter actualParentSObjectField; + try { + actualParentSObjectField = new ParentIdLookupFieldFormatter( + actualParentSObjectName + , selectedParentSObjectField.getRelationshipName() + , selectedIdLookupField); + } catch (RelationshipFormatException e) { + // TODO Auto-generated catch block + logger.error(e.getMessage()); + return null; + } + return actualParentSObjectField; + } + return null; + } + + /* + * (non-Javadoc) + * @see com.salesforce.dataloader.ui.LoadPage#setupPage() + */ + @Override + public boolean setupPagePostLogin() { + // set the required data + setReferenceObjects(); + + // Add the foreign key entity external id fields dropdowns + createFkExtIdUi(); + return true; + } + + public void setReferenceObjects() { + this.parentSObjectDescribeMap = controller.getReferenceDescribes(); + } + + @Override + public void setPageComplete() { + setPageComplete(true); + } + + @Override + protected OperationPage getNextPageOverride(){ + setupPage(); + if (!hasParentEntitiesWithIdLookupField) { + // nothing to display, go to the next page + return (OperationPage)getNextPage(); + } + return this; + } + + @Override + protected OperationPage getPreviousPageOverride(){ + if (!hasParentEntitiesWithIdLookupField) { + // nothing to display, go to the previous page + return (OperationPage)getPreviousPage(); + } + return this; + } + + public boolean setupPage() { + boolean success = super.setupPage(); + if (this.controller != null && this.controller.isLoggedIn()) { + String message = Labels.getFormattedString(this.getClass().getSimpleName() + ".pageMessage", this.controller.getAppConfig().getString(AppConfig.PROP_ENTITY)); + this.setMessage(message); + } + return success; + } + +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/ContentLimitLink.java b/src/main/java/com/salesforce/dataloader/ui/ContentLimitLink.java new file mode 100644 index 000000000..63a89b168 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/ContentLimitLink.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +package com.salesforce.dataloader.ui; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Listener; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.util.AppUtil; + +/** + * + */ +public class ContentLimitLink extends Link { + + /** + * @param parent + * @param style + */ + private Controller controller; + + public ContentLimitLink(Composite parent, int style, Controller controller) { + super(parent, style); + this.controller = controller; + this.setText(Labels.getString("UI.contentUploadLimit")); + this.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + URLUtil.openURL(e.text); + } + }); + + // based on https://www.informit.com/articles/article.aspx?p=354574&seqNum=3 + this.addListener(SWT.Paint, new Listener() { + public void handleEvent(Event e) { + setVisible(); + } + }); + } + + public void setVisible() { + AppConfig appConfig = controller.getAppConfig(); + String operation = appConfig.getString(AppConfig.PROP_OPERATION); + boolean isVisible = AppUtil.isContentSObject( + appConfig.getString(AppConfig.PROP_ENTITY)) + && operation != null + && ("insert".equalsIgnoreCase(operation) + || "update".equalsIgnoreCase(operation) + || "upsert".equalsIgnoreCase(operation)); + setVisible(isVisible); + } + + // Based on answer at https://stackoverflow.com/questions/4264983/why-is-subclassing-not-allowed-for-many-of-the-swt-controls + @Override + protected void checkSubclass() { + // allow subclass + } + +} diff --git a/src/main/java/com/salesforce/dataloader/ui/DLProgressMonitorDialog.java b/src/main/java/com/salesforce/dataloader/ui/DLProgressMonitorDialog.java new file mode 100644 index 000000000..c3ad794b5 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/DLProgressMonitorDialog.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + + +package com.salesforce.dataloader.ui; + +import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.swt.widgets.Shell; + +public class DLProgressMonitorDialog extends ProgressMonitorDialog { + + public DLProgressMonitorDialog(Shell parent) { + super(parent); + } + + protected boolean isResizable() { + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/DataSelectionDialog.java b/src/main/java/com/salesforce/dataloader/ui/DataSelectionDialog.java index 5626c1f44..622116014 100644 --- a/src/main/java/com/salesforce/dataloader/ui/DataSelectionDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/DataSelectionDialog.java @@ -30,28 +30,30 @@ import java.util.List; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.*; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.DataAccessObjectFactory; import com.salesforce.dataloader.dao.DataReader; import com.salesforce.dataloader.exception.DataAccessObjectException; -import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.util.DAORowUtil; -import com.sforce.ws.ConnectionException; -public class DataSelectionDialog extends Dialog { - private String message; - private boolean success; - private Controller controller; +public class DataSelectionDialog extends WizardDialog { private Button ok; private Label label; + private ContentLimitLink contentNoteLimitLink; + private String delimiterList = ""; + private String daoNameStr; + private String sObjectName; /** * InputDialog constructor @@ -59,154 +61,122 @@ public class DataSelectionDialog extends Dialog { * @param parent * the parent */ - public DataSelectionDialog(Shell parent, Controller controller) { - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - this.controller = controller; - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public DataSelectionDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - setText(Labels.getString("DataSelectionDialog.title")); //$NON-NLS-1$ - setMessage(Labels.getString("DataSelectionDialog.message")); //$NON-NLS-1$ + public DataSelectionDialog(Shell parent, Controller controller, String daoNameStr, String sObjectName) { + super(parent, controller); + this.daoNameStr = daoNameStr; + this.sObjectName = sObjectName; + if (controller.getAppConfig().getBoolean(AppConfig.PROP_CSV_DELIMITER_COMMA)) { + this.delimiterList = " ','"; + } + if (controller.getAppConfig().getBoolean(AppConfig.PROP_CSV_DELIMITER_TAB)) { + this.delimiterList += " ''"; + } + if (controller.getAppConfig().getBoolean(AppConfig.PROP_CSV_DELIMITER_OTHER)) { + String otherDelimiters = controller.getAppConfig().getString(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE); + for (char c : otherDelimiters.toCharArray()) { + this.delimiterList += " '" + c + "'"; + } + } } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; + + private void handleCSVReadError(Shell shell, String errorText) { + success = false; + ok.setEnabled(true); + Point size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT); + label.setSize(shell.getClientArea().width, size.y); + label.setText(errorText); //$NON-NLS-1$ + logger.error(errorText); + + shell.setText(Labels.getString("DataSelectionDialog.titleError")); + shell.pack(); + shell.redraw(); } + + // called from BaseDialog.open() + protected void processingWithBusyIndicator(Shell shell) { + try { + getController().initializeOperation( + DataAccessObjectFactory.CSV_READ_TYPE, + daoNameStr, + sObjectName); + } catch (MappingInitializationException e) { + handleCSVReadError(shell, + Labels.getString("DataSelectionDialog.errorRead") + + Labels.getFormattedString("DataSelectionDialog.errorReadExceptionDetails", + e.getMessage()) + ); + return; + } - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } + String daoPath = getController().getAppConfig().getString(AppConfig.PROP_DAO_NAME); + File file = new File(daoPath); - /** - * Opens the dialog and returns the input - * - * @return String - */ - public boolean open() { - // Create the dialog window - final Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - BusyIndicator.showWhile(display, new Thread() { - @Override - public void run() { - try { - controller.setFieldTypes(); - controller.setReferenceDescribes(); - - String daoPath = controller.getConfig().getString(Config.DAO_NAME); - File file = new File(daoPath); - - if (!file.exists() || !file.canRead()) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("DataSelectionDialog.errorRead")); //$NON-NLS-1$ - shell.setText(Labels.getString("DataSelectionDialog.titleError")); - return; - } - - try { - controller.createDao(); - } catch (DataAccessObjectInitializationException e) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("DataSelectionDialog.errorRead")); //$NON-NLS-1$ - shell.setText(Labels.getString("DataSelectionDialog.titleError")); - return; - } - DataReader dataReader = (DataReader)controller.getDao(); - - List header = null; - int totalRows = 0; - try { - dataReader.checkConnection(); - dataReader.open(); - - String warning = DAORowUtil.validateColumns(dataReader); - if(warning != null && warning.length() != 0) { - int response = UIUtils.warningConfMessageBox(shell, warning + "\n" + Labels.getString("DataSelectionDialog.warningConf")); - // in case user doesn't want to continue, treat this as an error - if(response != SWT.YES) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("DataSelectionDialog.errorCSVFormat")); //$NON-NLS-1$ - shell.setText(Labels.getString("DataSelectionDialog.titleError")); - return; - } - } - - totalRows = dataReader.getTotalRows(); - - if ((header = dataReader.getColumnNames())== null || header.size() == 0) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("DataSelectionDialog.errorCSVFormat")); //$NON-NLS-1$ - shell.setText(Labels.getString("DataSelectionDialog.titleError")); - return; - } - - } catch (DataAccessObjectException e) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("DataSelectionDialog.errorCSVFormat") + " " + e.getMessage()); //$NON-NLS-1$ - Point size = label.computeSize(SWT.DEFAULT, SWT.DEFAULT); - label.setSize(shell.getClientArea().width, size.y); - shell.setText(Labels.getString("DataSelectionDialog.titleError")); - shell.pack(); - shell.redraw(); - return; - } finally { - dataReader.close(); - } - success = true; - ok.setEnabled(true); - label.setText(Labels.getFormattedString( - "DataSelectionDialog.initSuccess", String.valueOf(totalRows))); //$NON-NLS-1$ - label.getParent().pack(); - - } catch (ConnectionException ex) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("DataSelectionDialog.errorEntity")); //$NON-NLS-1$ - shell.setText(Labels.getString("DataSelectionDialog.titleError")); + if (!file.exists() || !file.canRead()) { + handleCSVReadError(shell, + Labels.getString("DataSelectionDialog.errorRead") + + Labels.getString("DataSelectionDialog.errorReadPermissionDetails") + ); + return; + } + DataReader dataReader = (DataReader)getController().getDao(); + + List header = null; + int totalRows = 0; + try { + dataReader.open(); + + String error = DAORowUtil.validateColumns(dataReader); + if(error != null && error.length() != 0) { + int response = UIUtils.errorMessageBox(shell, error); + // in case user doesn't want to continue, treat this as an error + if(response != SWT.YES) { + handleCSVReadError(shell, + Labels.getString("DataSelectionDialog.errorCSVFormat") + + Labels.getFormattedString("DataSelectionDialog.errorCSVDetails", + delimiterList)); return; } } - }); + totalRows = dataReader.getTotalRows(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); + if ((header = dataReader.getColumnNames())== null || header.size() == 0) { + handleCSVReadError(shell, + Labels.getString("DataSelectionDialog.errorCSVFormat") + + Labels.getFormattedString("DataSelectionDialog.errorCSVDetails", + delimiterList)); + return; } + + } catch (DataAccessObjectException e) { + handleCSVReadError(shell, + Labels.getString("DataSelectionDialog.errorCSVFormat") + + Labels.getFormattedString("DataSelectionDialog.errorCSVDetails", + delimiterList)); + return; + } finally { + dataReader.close(); } - // Return the sucess - return success; + success = true; + ok.setEnabled(true); + String apiInfoStr = getController().getAPIInfo(); + + // Set the description + label.setText(Labels.getFormattedString( + "DataSelectionDialog.initSuccess", String.valueOf(totalRows)) + + "\n\n" + + Labels.getString("LoadPage.importBatchSize") + + " " + + getController().getAppConfig().getMaxRowsInImportBatch() + + "\n" + + Labels.getString("AdvancedSettingsDialog.uiLabel." + AppConfig.PROP_LOAD_ROW_TO_START_AT) + + " " + + getController().getAppConfig().getString(AppConfig.PROP_LOAD_ROW_TO_START_AT) + + "\n" + + apiInfoStr + ); //$NON-NLS-1$ + + label.getParent().pack(); } /** @@ -215,18 +185,24 @@ public void run() { * @param shell * the dialog window */ - private void createContents(final Shell shell) { + protected void createContents(final Shell shell) { GridLayout layout = new GridLayout(2, false); layout.verticalSpacing = 10; shell.setLayout(layout); label = new Label(shell, SWT.WRAP); - label.setText(message); + label.setText(getMessage()); GridData labelData = new GridData(); labelData.horizontalSpan = 2; labelData.widthHint = 400; label.setLayoutData(labelData); + + contentNoteLimitLink = new ContentLimitLink(shell, SWT.WRAP, getController()); + GridData linkData = new GridData(); + linkData.horizontalSpan = 2; + linkData.widthHint = 400; + contentNoteLimitLink.setLayoutData(linkData); //the bottom separator Label labelSeparatorBottom = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL); diff --git a/src/main/java/com/salesforce/dataloader/ui/DataSelectionPage.java b/src/main/java/com/salesforce/dataloader/ui/DataSelectionPage.java index 4859eff08..011d09d7f 100644 --- a/src/main/java/com/salesforce/dataloader/ui/DataSelectionPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/DataSelectionPage.java @@ -29,24 +29,20 @@ import java.util.*; import java.util.Map.Entry; -import org.apache.log4j.Logger; + import org.eclipse.jface.preference.FileFieldEditor; import org.eclipse.jface.util.IPropertyChangeListener; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.*; import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; + import org.eclipse.swt.graphics.Color; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.DataAccessObjectFactory; -import com.salesforce.dataloader.ui.entitySelection.*; import com.sforce.soap.partner.DescribeGlobalSObjectResult; /** @@ -57,26 +53,14 @@ */ public class DataSelectionPage extends LoadPage { - private final Logger logger = Logger.getLogger(DataSelectionPage.class); - - private final Controller controller; - // These filter extensions are used to filter which files are displayed. private static final String[] FILTER_EXTS = { "*.csv" }; //$NON-NLS-1$ - private final EntityFilter filter = new EntityFilter(); private ListViewer lv; private FileFieldEditor csvChooser; public DataSelectionPage(Controller controller) { - super(Labels.getString("DataSelectionPage.data"), Labels.getString("DataSelectionPage.dataMsg"), UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - this.controller = controller; - - // Set the description - setDescription(Labels.getString("DataSelectionPage.message")); //$NON-NLS-1$ - - setPageComplete(false); + super("DataSelectionPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ } @Override @@ -91,55 +75,9 @@ public void createControl(Composite parent) { Composite comp = new Composite(parent, SWT.NONE); comp.setLayout(gridLayout); - - Label label = new Label(comp, SWT.RIGHT); - label.setText(Labels.getString("DataSelectionPage.selectObject")); //$NON-NLS-1$ - GridData data = new GridData(); - label.setLayoutData(data); - - // Add a checkbox to toggle filter - Button filterAll = new Button(comp, SWT.CHECK); - filterAll.setText(Labels.getString("DataSelectionPage.showAll")); //$NON-NLS-1$ - data = new GridData(); - filterAll.setLayoutData(data); - - lv = new ListViewer(comp, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); - lv.setContentProvider(new EntityContentProvider()); - lv.setLabelProvider(new EntityLabelProvider()); - lv.setInput(null); - data = new GridData(GridData.FILL, GridData.FILL, true, true); - data.heightHint = 140; - data.widthHint = 140; - lv.getControl().setLayoutData(data); - lv.addFilter(filter); - lv.setSorter(new EntityViewerSorter()); - - lv.addSelectionChangedListener(new ISelectionChangedListener() { - @Override - public void selectionChanged(SelectionChangedEvent event) { - checkPageComplete(); - } - - }); - - //if we're logged in, set the input - if (controller.isLoggedIn()) { - setInput(controller.getEntityDescribes()); - } - - filterAll.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - if (((Button)event.widget).getSelection()) - lv.removeFilter(filter); - else - lv.addFilter(filter); - } - }); - - new Label(comp, SWT.NONE); - - final String infoMessage = this.controller.getConfig().getOperationInfo().getInfoMessageForDataSelectionPage(); + GridData data = new GridData(GridData.FILL_BOTH); + comp.setLayoutData(data); + final String infoMessage = this.controller.getAppConfig().getOperationInfo().getInfoMessageForDataSelectionPage(); if (infoMessage != null) { Label l = new Label(comp, SWT.RIGHT); GridData gd = new GridData(); @@ -148,6 +86,17 @@ public void widgetSelected(SelectionEvent event) { l.setText(infoMessage); l.setForeground(new Color(getShell().getDisplay(), 0xff, 0, 0)); } + + lv = EntitySelectionListViewerUtil.getEntitySelectionListViewer(this.getClass(), comp, this.controller.getAppConfig()); + lv.addSelectionChangedListener(new ISelectionChangedListener() { + @Override + public void selectionChanged(SelectionChangedEvent event) { + setPageComplete(); + } + + }); + + setupPage(); new Label(comp, SWT.NONE); @@ -155,7 +104,6 @@ public void widgetSelected(SelectionEvent event) { Composite compChooser = new Composite(comp, SWT.NONE); data = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_END); - data.widthHint = 400; compChooser.setLayoutData(data); csvChooser = new FileFieldEditor( @@ -170,11 +118,11 @@ public void propertyChange(PropertyChangeEvent event) { if (!((Boolean)event.getNewValue()).booleanValue()) { setErrorMessage(Labels.getString("DataSelectionPage.selectValid")); //$NON-NLS-1$ - checkPageComplete(); + setPageComplete(); } else { setErrorMessage(null); - checkPageComplete(); + setPageComplete(); } } catch (ClassCastException cle) { logger.error(Labels.getString("DataSelectionPage.errorClassCast"), cle); //$NON-NLS-1$ @@ -189,8 +137,8 @@ public void propertyChange(PropertyChangeEvent event) { /** * Function to dynamically set the entity list */ - private void setInput(Map entityDescribes) { - OperationInfo operation = controller.getConfig().getOperationInfo(); + private void setInput(Map entityDescribes) { + OperationInfo operation = controller.getAppConfig().getOperationInfo(); Map inputDescribes = new HashMap(); // for each object, check whether the object is valid for the current @@ -199,7 +147,7 @@ private void setInput(Map entityDescribes) for (Entry entry : entityDescribes.entrySet()) { String objectName = entry.getKey(); DescribeGlobalSObjectResult objectDesc = entry.getValue(); - if (operation.isDelete() && objectDesc.isDeletable()) { + if ((operation.isDelete() || operation.isUndelete()) && objectDesc.isDeletable()) { inputDescribes.put(objectName, objectDesc); } else if (operation == OperationInfo.insert && objectDesc.isCreateable()) { inputDescribes.put(objectName, objectDesc); @@ -211,9 +159,8 @@ private void setInput(Map entityDescribes) } } lv.setInput(inputDescribes); - lv.refresh(); lv.getControl().getParent().pack(); - + lv.refresh(); } private boolean checkEntityStatus() { @@ -226,8 +173,7 @@ private boolean checkEntityStatus() { } - private void checkPageComplete() { - + public void setPageComplete() { if (csvChooser.isValid() && checkEntityStatus()) { setPageComplete(true); } else { @@ -244,22 +190,26 @@ private void checkPageComplete() { @Override public LoadPage getNextPage() { - //attempt to login - Config config = controller.getConfig(); //get entity IStructuredSelection selection = (IStructuredSelection)lv.getSelection(); - DescribeGlobalSObjectResult entity = (DescribeGlobalSObjectResult)selection.getFirstElement(); - - config.setValue(Config.ENTITY, entity.getName()); - // set DAO - CSV file name - config.setValue(Config.DAO_NAME, csvChooser.getStringValue()); - // set DAO type to CSV - config.setValue(Config.DAO_TYPE, DataAccessObjectFactory.CSV_READ_TYPE); - controller.saveConfig(); - - DataSelectionDialog dlg = new DataSelectionDialog(getShell(), controller); + DescribeGlobalSObjectResult selectedEntity = (DescribeGlobalSObjectResult)selection.getFirstElement(); + DataSelectionDialog dlg = new DataSelectionDialog( + getShell(), + controller, + csvChooser.getStringValue(), + selectedEntity.getName()); if (dlg.open()) { - return super.getNextPage(); + LoadPage nextPage = super.getNextPage(); + LoadPage nextNextPage = nextPage.getNextPage(); + if (Controller.getAPIMajorVersion() < 61 + && controller.getAppConfig().getOperationInfo() != OperationInfo.upsert) { + if (nextPage instanceof ExternalIdPage) { + nextPage.setPageComplete(true); + nextPage.dispose(); + nextPage = nextNextPage; + } + } + return nextPage; } else { return this; } @@ -270,13 +220,12 @@ public LoadPage getNextPage() { * @see com.salesforce.dataloader.ui.LoadPage#setupPage() */ @Override - boolean setupPage() { - Map describes = controller - .getEntityDescribes(); - if(describes != null) { - setInput(describes); - return true; + public boolean setupPagePostLogin() { + Map describes = controller.getEntityDescribes(); + if(describes == null) { + return false; } - return false; + setInput(describes); + return true; } } diff --git a/src/main/java/com/salesforce/dataloader/ui/EntitySelectionListViewerUtil.java b/src/main/java/com/salesforce/dataloader/ui/EntitySelectionListViewerUtil.java new file mode 100644 index 000000000..5bfca5ec3 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/EntitySelectionListViewerUtil.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import org.eclipse.jface.viewers.ListViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Text; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.ui.entitySelection.EntityContentProvider; +import com.salesforce.dataloader.ui.entitySelection.EntityFilter; +import com.salesforce.dataloader.ui.entitySelection.EntityLabelProvider; +import com.salesforce.dataloader.ui.entitySelection.EntityViewerComparator; + +public class EntitySelectionListViewerUtil { + private static final String PROPERTIES_PREFIX_STR = "DataSelectionPage"; + + public static ListViewer getEntitySelectionListViewer(Class pageClass, Composite comp, AppConfig appConfig) { + String propertiesPrefixStr = PROPERTIES_PREFIX_STR; + Label label = new Label(comp, SWT.RIGHT); + label.setText(Labels.getString(pageClass.getSimpleName() + ".selectObject")); //$NON-NLS-1$ + GridData data = new GridData(); + label.setLayoutData(data); + + Text search = new Text(comp, SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH); + data = new GridData(GridData.FILL_HORIZONTAL); + search.setLayoutData(data); + + EntityFilter filter = new EntityFilter(search); + final ListViewer listViewer = new ListViewer(comp, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); + listViewer.setContentProvider(new EntityContentProvider()); + listViewer.setLabelProvider(new EntityLabelProvider()); + listViewer.setInput(null); + data = new GridData(GridData.FILL_BOTH); + data.heightHint = AppConfig.DEFAULT_WIZARD_HEIGHT; + listViewer.getControl().setLayoutData(data); + listViewer.addFilter(filter); + listViewer.setComparator(new EntityViewerComparator()); + + // Add a checkbox to toggle filter + Button filterAll = new Button(comp, SWT.CHECK); + filter.setFilterButtion(filterAll); + filterAll.setText(Labels.getString(propertiesPrefixStr + ".showAll")); //$NON-NLS-1$ + filterAll.setToolTipText(Labels.getString(propertiesPrefixStr + ".showAllToolTip")); + + + filterAll.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + listViewer.refresh(); + } + }); + + search.addSelectionListener(new SelectionAdapter() { + public void widgetDefaultSelected(SelectionEvent e) { + listViewer.refresh(); + } + }); + + search.addListener(SWT.KeyUp, new Listener() { + public void handleEvent(Event e) { + listViewer.refresh(); + } + }); + + return listViewer; + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/ExternalIdPage.java b/src/main/java/com/salesforce/dataloader/ui/ExternalIdPage.java index aa88d0438..5d862479c 100644 --- a/src/main/java/com/salesforce/dataloader/ui/ExternalIdPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/ExternalIdPage.java @@ -35,7 +35,8 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.sforce.soap.partner.DescribeSObjectResult; import com.sforce.soap.partner.Field; @@ -48,23 +49,13 @@ */ public class ExternalIdPage extends LoadPage { - private final Controller controller; private Composite comp; private Combo extIdFieldCombo; private Label labelExtId; private Label labelExtIdInfo; public ExternalIdPage(Controller controller) { - super(Labels.getString("ExternalIdPage.title"), //$NON-NLS-1$ - Labels.getString("ExternalIdPage.message"), //$NON-NLS-1$ - UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ - - this.controller = controller; - - // Set the description - setDescription(Labels.getString("ExternalIdPage.description")); //$NON-NLS-1$ - - setPageComplete(false); + super("ExternalIdPage", controller); //$NON-NLS-1$ } @Override @@ -106,12 +97,7 @@ public void handleEvent(Event e) { extIdFieldCombo.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent arg0) { - String fieldName = extIdFieldCombo.getText(); - if (fieldName != null && !fieldName.equals("") ) { //$NON-NLS-1$ - setPageComplete(true); - } else { - setPageComplete(false); - } + setPageComplete(); } @Override public void widgetDefaultSelected(SelectionEvent arg0) { @@ -131,17 +117,14 @@ public boolean setExtIdCombo() { extIdFieldCombo.setEnabled(true); // entity is known at this time, set the correct combo label - labelExtId.setText(Labels.getFormattedString("ExternalIdPage.externalIdComboText", controller.getConfig().getString(Config.ENTITY))); //$NON-NLS-1$ + labelExtId.setText(Labels.getFormattedString("ExternalIdPage.externalIdComboText", controller.getAppConfig().getString(AppConfig.PROP_ENTITY))); //$NON-NLS-1$ DescribeSObjectResult fieldTypes = controller.getFieldTypes(); Field[] fields = fieldTypes.getFields(); ArrayList extIdFields = new ArrayList(); for(Field field : fields) { - // salesforce id can be used for upsert in addition to external id fields - if("id".equals(field.getName().toLowerCase())) { - extIdFields.add(field.getName()); - } - if(field.isExternalId() && (field.isCreateable() || field.isUpdateable())) { + // every idLookup field can be used for upserts, including Id and Name + if(field.isIdLookup()) { extIdFields.add(field.getName()); } } @@ -152,12 +135,11 @@ public boolean setExtIdCombo() { if(extIdFieldCombo.getItemCount() == 1) { extIdFieldCombo.setEnabled(false); extIdFieldCombo.setText(extIdNames[0]); - labelExtIdInfo.setText(Labels.getFormattedString("ExternalIdPage.externalIdInfoNoExtId", controller.getConfig().getString(Config.ENTITY))); //$NON-NLS-1$ - setPageComplete(true); + labelExtIdInfo.setText(Labels.getFormattedString("ExternalIdPage.externalIdInfoNoExtId", controller.getAppConfig().getString(AppConfig.PROP_ENTITY))); //$NON-NLS-1$ } else { - labelExtIdInfo.setText(Labels.getFormattedString("ExternalIdPage.externalIdInfoExtIdExists", controller.getConfig().getString(Config.ENTITY))); //$NON-NLS-1$ - setPageComplete(false); + labelExtIdInfo.setText(Labels.getFormattedString("ExternalIdPage.externalIdInfoExtIdExists", controller.getAppConfig().getString(AppConfig.PROP_ENTITY))); //$NON-NLS-1$ } + setPageComplete(); comp.layout(); if (extIdNames.length > 0 ) { @@ -178,15 +160,15 @@ public LoadPage getNextPage() { // prepare next page LoadPage nextPage = null; - ForeignKeyExternalIdPage fkExtIdPage = (ForeignKeyExternalIdPage) getWizard().getPage(Labels.getString("ForeignKeyExternalIdPage.title")); //$NON-NLS-1$ - if(controller.getReferenceDescribes().size() > 0) { - fkExtIdPage.setPageComplete(true); + ChooseLookupFieldForRelationshipPage fkExtIdPage = (ChooseLookupFieldForRelationshipPage) getWizard().getPage(ChooseLookupFieldForRelationshipPage.class.getSimpleName()); //$NON-NLS-1$ + if(controller.getAppConfig().getOperationInfo() != OperationInfo.delete + && controller.getReferenceDescribes().size() > 0) { nextPage = fkExtIdPage; } else { - fkExtIdPage.setPageComplete(true); - nextPage = (LoadPage)getWizard().getPage(Labels.getString("MappingPage.title")); //$NON-NLS-1$ + nextPage = (LoadPage)getWizard().getPage(MappingPage.class.getSimpleName()); //$NON-NLS-1$ } nextPage.setupPage(); + nextPage.setPageComplete(); return nextPage; } @@ -195,11 +177,11 @@ public LoadPage getNextPage() { * @param selectedObjects */ private void saveExtIdData() { - Config config = controller.getConfig(); + AppConfig appConfig = controller.getAppConfig(); // external id field String extIdField = extIdFieldCombo.getText(); - config.setValue(Config.EXTERNAL_ID_FIELD, extIdField); + appConfig.setValue(AppConfig.PROP_IDLOOKUP_FIELD, extIdField); controller.saveConfig(); } @@ -209,7 +191,7 @@ private void saveExtIdData() { * @see com.salesforce.dataloader.ui.LoadPage#setupPage() */ @Override - boolean setupPage() { + public boolean setupPagePostLogin() { if (!setExtIdCombo()) { //if there is no external id, don't let them continue. MessageBox msg = new MessageBox(getShell(), SWT.ICON_ERROR | SWT.OK); @@ -217,8 +199,17 @@ boolean setupPage() { msg.setText(Labels.getString("ExternalIdPage.errorExternalIdRequiredTitle")); msg.open(); return false; - } else { - return true; } + return true; + } + + @Override + public void setPageComplete() { + String fieldName = extIdFieldCombo.getText(); + if (fieldName != null && !fieldName.equals("") ) { //$NON-NLS-1$ + setPageComplete(true); + } else { + setPageComplete(false); + } } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/FinishPage.java b/src/main/java/com/salesforce/dataloader/ui/FinishPage.java index 1fc892d9a..dbef8d101 100644 --- a/src/main/java/com/salesforce/dataloader/ui/FinishPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/FinishPage.java @@ -27,15 +27,19 @@ package com.salesforce.dataloader.ui; import org.eclipse.jface.preference.DirectoryFieldEditor; +import org.eclipse.jface.wizard.IWizardContainer; import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.mapping.LoadMapper; +import com.salesforce.dataloader.mapping.Mapper; /** * Describe your class here. @@ -45,20 +49,17 @@ */ public class FinishPage extends LoadPage { - private final Controller controller; private DirectoryFieldEditor dirFE; + private ContentLimitLink contentNoteLimitLink; public FinishPage(Controller controller) { - super(Labels.getString("FinishPage.title"), Labels.getString("FinishPage.finishMsg"), UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - this.controller = controller; - setPageComplete(false); - - // Set the description - setDescription(Labels.getString("FinishPage.selectDir")); //$NON-NLS-1$ - + this("FinishPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ } + public FinishPage(String name, Controller controller) { + super(name, controller); //$NON-NLS-1$ //$NON-NLS-2$ + } + @Override public void createControl(Composite parent) { Composite comp = new Composite(parent, SWT.NONE); @@ -73,16 +74,27 @@ public void createControl(Composite parent) { label.setText(Labels.getString("FinishPage.overwritten")); //$NON-NLS-1$ Composite dirComp = new Composite(comp, SWT.NONE); - GridData data = new GridData(); - data.widthHint = 400; + GridData data = new GridData(GridData.FILL_HORIZONTAL); dirComp.setLayoutData(data); dirFE = new DirectoryFieldEditor(Labels.getString("FinishPage.output"), Labels.getString("FinishPage.chooseDir"), dirComp); //$NON-NLS-1$ //$NON-NLS-2$ - dirFE.setStringValue(controller.getConfig().getString(Config.OUTPUT_STATUS_DIR)); + dirFE.setStringValue(controller.getAppConfig().getString(AppConfig.PROP_OUTPUT_STATUS_DIR)); - hook_createControl(comp); + Text textField = dirFE.getTextControl(dirComp); + textField.addModifyListener(new ModifyListener() { + + @Override + public void modifyText(ModifyEvent arg0) { + setPageComplete(); + } + + }); + contentNoteLimitLink = new ContentLimitLink(comp, SWT.WRAP, getController()); + + hook_createControl(comp); setControl(comp); + setupPage(); } protected void hook_createControl(Composite comp) {} @@ -101,9 +113,12 @@ public boolean canFlipToNextPage() { * @see com.salesforce.dataloader.ui.LoadPage#setupPage() */ @Override - boolean setupPage() { + protected boolean setupPagePostLogin() { try { verifySettings(); + if (!controller.getAppConfig().getBoolean(AppConfig.PROP_WIZARD_POPULATE_RESULTS_FOLDER_WITH_PREVIOUS_OP_RESULTS_FOLDER)) { + dirFE.setStringValue(null); // clear previous selection + } } catch (final MappingInitializationException e) { final FinishPage page = this; Display.getDefault().syncExec(new Thread() { @@ -114,14 +129,24 @@ public void run() { }); return false; } + + setPageComplete(); + IWizardContainer wizardContainer = this.getContainer(); + if (wizardContainer != null) { + wizardContainer.updateButtons(); + } + contentNoteLimitLink.setVisible(); if (!controller.saveConfig()) return false; - setPageComplete(true); return true; } private void verifySettings() throws MappingInitializationException { - if (!getController().getConfig().getOperationInfo().isExtraction()) - ((LoadMapper)getController().getMapper()).verifyMappingsAreValid(); + if (!getController().getAppConfig().getOperationInfo().isExtraction()) { + Mapper mapper = (LoadMapper)getController().getMapper(); + if (mapper != null) { + ((LoadMapper)getController().getMapper()).verifyMappingsAreValid(); + } + } } public boolean finishAllowed() { @@ -132,4 +157,12 @@ protected Controller getController() { return this.controller; } + public void setPageComplete() { + String outputDir = getOutputDir(); + if (outputDir == null || outputDir.isBlank() || !this.isCurrentPage()) { + setPageComplete(false); + } else { + setPageComplete(true); + } + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java b/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java deleted file mode 100644 index d57b9bcd5..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/ForeignKeyExternalIdPage.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui; - -import java.util.*; -import java.util.List; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.layout.*; -import org.eclipse.swt.widgets.*; - -import com.salesforce.dataloader.client.DescribeRefObject; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dyna.ObjectField; -import com.sforce.soap.partner.Field; - -/** - * Page for selecting external id fields for the foreign key objects - * - * @author Alex Warshavsky - * @since 8.0 - */ -public class ForeignKeyExternalIdPage extends LoadPage { - - private final Controller controller; - private final Map extIdSelections = new HashMap(); - private Composite containerComp; - private ScrolledComposite scrollComp; - private Map referenceObjects; - - public ForeignKeyExternalIdPage(Controller controller) { - super(Labels.getString("ForeignKeyExternalIdPage.title"), //$NON-NLS-1$ - Labels.getString("ForeignKeyExternalIdPage.message"), //$NON-NLS-1$ - UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ - - this.controller = controller; - - // Set the description - setDescription(Labels.getString("ForeignKeyExternalIdPage.description")); //$NON-NLS-1$ - - setPageComplete(true); - } - - @Override - public void createControl(Composite parent) { - containerComp = new Composite(parent, SWT.NONE); - containerComp.setLayout(new FillLayout()); - - setControl(containerComp); - - setPageComplete(true); - } - - private void createFkExtIdUi() { - getShell().setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - - if(scrollComp != null) { - scrollComp.dispose(); - } - scrollComp = new ScrolledComposite(containerComp, SWT.V_SCROLL); - scrollComp.setExpandHorizontal(true); - scrollComp.setExpandVertical(true); - Composite comp = new Composite(scrollComp, SWT.NONE); - scrollComp.setContent(comp); - - GridLayout gridLayout = new GridLayout(2, false); - gridLayout.horizontalSpacing = 10; - gridLayout.marginHeight = 20; - gridLayout.verticalSpacing = 7; - comp.setLayout(gridLayout); - - scrollComp.setMinSize(comp.computeSize(SWT.DEFAULT, SWT.DEFAULT)); - - // set scrolling specific to data - ScrollBar scrollBar = scrollComp.getVerticalBar(); - scrollBar.setIncrement(20); - scrollBar.setPageIncrement(20 * 5); - - extIdSelections.clear(); - if(referenceObjects != null) { - for(String relationshipName : referenceObjects.keySet()) { - createObjectExtIdUi(comp, relationshipName); - } - } - scrollComp.setMinSize(comp.computeSize(SWT.DEFAULT, SWT.DEFAULT)); - - containerComp.layout(); - - setPageComplete(true); - } - - /** - * Create UI components for one object: label and a combo box with external id fields - * @param comp - * @param relationshipName - */ - private void createObjectExtIdUi(Composite comp, String relationshipName) { - Label labelExtId = new Label(comp, SWT.RIGHT); - labelExtId.setText(relationshipName); - labelExtId.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); - - // Add the ext id dropdown - Combo extIdCombo = new Combo(comp, SWT.DROP_DOWN | SWT.READ_ONLY); - - // The width comes out based on number of pixels, not characters - GridData extIdData = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING); - extIdData.widthHint = 150; - extIdCombo.setLayoutData(extIdData); - - // get object's ext id info & set combo box to list of external id fields - // set the objects reference information - DescribeRefObject extIdInfo = referenceObjects.get(relationshipName); - List fieldList = new ArrayList(extIdInfo.getFieldInfoMap().keySet()); - // add default selection "not selected" to the list to allow users to go back to it - fieldList.add(Labels.getString("ForeignKeyExternalIdPage.defaultComboText")); - UIUtils.setComboItems(extIdCombo, fieldList, Labels.getString("ForeignKeyExternalIdPage.defaultComboText")); - - extIdSelections.put(relationshipName, extIdCombo); - } - - /** - * Returns the next page, sets the external id - * - * @return IWizardPage - */ - @Override - public MappingPage getNextPage() { - // save data from this page and remember field selections for mapping - Map relatedFields = saveExtIdData(); - - MappingPage nextPage = (MappingPage)getWizard().getPage(Labels.getString("MappingPage.title")); //$NON-NLS-1$ - nextPage.setRelatedFields(relatedFields); - nextPage.setupPage(); - return nextPage; - } - - /** - * Save the data from this page in the config and return the selected external id field info - * @return selected objects external id field info - */ - private Map saveExtIdData() { - Map relatedFields = new HashMap(); - - // foreign key references (if any set) - Map extIdReferences = new HashMap(); - for(String relationshipName : extIdSelections.keySet()) { - Combo combo = extIdSelections.get(relationshipName); - String extIdFieldName = combo.getText(); - // make sure that the item selection has occurred and that the default text is not displayed anymore - if(extIdFieldName != null && extIdFieldName.length() > 0 - && ! extIdFieldName.equals(Labels.getString("ForeignKeyExternalIdPage.defaultComboText"))) { - DescribeRefObject refObjectInfo = referenceObjects.get(relationshipName); - extIdReferences.put(relationshipName, ObjectField.formatAsString(refObjectInfo.getObjectName(), extIdFieldName)); - relatedFields.put(relationshipName,refObjectInfo.getFieldInfoMap().get(extIdFieldName)); - } - } - - return relatedFields; - } - - /* - * (non-Javadoc) - * @see com.salesforce.dataloader.ui.LoadPage#setupPage() - */ - @Override - boolean setupPage() { - // set the required data - setReferenceObjects(); - - // Add the foreign key entity external id fields dropdowns - createFkExtIdUi(); - - return true; - } - - public void setReferenceObjects() { - this.referenceObjects = controller.getReferenceDescribes(); - } -} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/Grid12.java b/src/main/java/com/salesforce/dataloader/ui/Grid12.java index db014e444..244e5cd7d 100644 --- a/src/main/java/com/salesforce/dataloader/ui/Grid12.java +++ b/src/main/java/com/salesforce/dataloader/ui/Grid12.java @@ -27,15 +27,12 @@ package com.salesforce.dataloader.ui; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.CCombo; -import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; import java.util.ArrayList; -import java.util.Random; /** * Grid12 simplifies the creation of grid based layout @@ -43,29 +40,19 @@ public class Grid12 { private Composite composite; private final int columnWidth; - private final int cellHeight; private final GridLayout root; private boolean grabExcessHorizontalSpace; private boolean grabExcessVerticalSpace; - public Grid12(Composite composite, int columnWidth){ - this(composite, columnWidth, -1); - } - public Grid12(Composite composite, int columnWidth, int cellHeight){ - this(composite, columnWidth, cellHeight, false, true); - } - public Grid12(Composite composite, int columnWidth, int cellHeight, boolean grabExcessVerticalSpace, boolean grabExcessHorizontalSpace) { + public Grid12(Composite composite, int columnWidth, boolean grabExcessVerticalSpace, boolean grabExcessHorizontalSpace) { this.composite = composite; this.columnWidth = columnWidth; - this.cellHeight = cellHeight; this.root = new GridLayout(12, true); this.composite.setLayout(root); this.grabExcessVerticalSpace = grabExcessVerticalSpace; this.grabExcessHorizontalSpace = grabExcessHorizontalSpace; } - - public GridLayout getRoot() { return root; } @@ -79,7 +66,6 @@ public GridData createCell(int columnSpan, int horizontalAlignment) { data.horizontalSpan = columnSpan; data.widthHint = columnSpan * this.columnWidth; data.horizontalAlignment = horizontalAlignment; - data.heightHint = cellHeight; data.grabExcessHorizontalSpace = grabExcessHorizontalSpace; data.grabExcessVerticalSpace = grabExcessVerticalSpace; @@ -90,14 +76,14 @@ public int getColumnWidth() { return columnWidth; } - public int getCellHeight() { - return cellHeight; - } - public Label createLabel(int columns, String message) { - return createLabel(columns, message, SWT.RIGHT); + return createLabel(columns, message, SWT.RIGHT | SWT.WRAP); } + public Label createLeftLabel(int columns, String message) { + return createLabel(columns, message, SWT.LEFT | SWT.WRAP); + } + public Label createLabel(int columns, String message, int style) { Label label = new Label(composite, style); label.setText(message); @@ -147,9 +133,8 @@ public Button createButton(int columns, int style, String content) { return button; } - public CCombo createCombo(int columns, int style, ArrayList labels) { - CCombo combo = new CCombo(composite, style); - combo.setEditable(false); + public Combo createCombo(int columns, int style, ArrayList labels) { + Combo combo = new Combo(composite, style); combo.setLayoutData(createCell(columns)); for (String label: labels) { combo.add(label); diff --git a/src/main/java/com/salesforce/dataloader/ui/HardDeleteFinishPage.java b/src/main/java/com/salesforce/dataloader/ui/HardDeleteFinishPage.java index 6e97c39f9..06bae4554 100644 --- a/src/main/java/com/salesforce/dataloader/ui/HardDeleteFinishPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/HardDeleteFinishPage.java @@ -54,7 +54,7 @@ private void setCanFinish(boolean selection) { @Override protected void hook_createControl(Composite comp) { super.hook_createControl(comp); - if (getController().getConfig().getOperationInfo() == OperationInfo.hard_delete) { + if (getController().getAppConfig().getOperationInfo() == OperationInfo.hard_delete) { Composite terms = new Composite(comp, SWT.NONE); GridLayout layout = new GridLayout(1, false); terms.setLayout(layout); diff --git a/src/main/java/com/salesforce/dataloader/ui/Hyperlink.java b/src/main/java/com/salesforce/dataloader/ui/Hyperlink.java deleted file mode 100644 index 6b094b3e9..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/Hyperlink.java +++ /dev/null @@ -1,614 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui; - -import org.eclipse.swt.*; -import org.eclipse.swt.events.*; -import org.eclipse.swt.graphics.*; -import org.eclipse.swt.widgets.*; - - -/** - * A hyperlink text label. - *

- * This control displays a line of text (with an optional underline) which can - * be clicked to send a Selection event. Colors for the text and underline in - * their normal, mouse hover and active state can be set independently. The text - * can contain a mnemonic character for triggering the link via keyboard. Unless - * the control is created with the NO_FOCUS style, it accepts keyboard focus and - * can be triggered with RETURN and SPACE while focused. - *

- * Note: This control should not be resized beyond its minimum / preferred size. - *

- *

- *
Styles: - *
NO_FOCUS
- *
Events: - *
Selection
- *
- *

- * - * @author Stefan Zeiger (szeiger@novocode.com) - * @since Mar 2, 2004 - * @version $Id: Hyperlink.java,v 1.5 2004/03/10 20:39:19 szeiger Exp $ - */ - -public final class Hyperlink extends Canvas -{ - private String text = ""; - private Cursor handCursor, arrowCursor; - private Color normalForeground, activeForeground, hoverForeground; - private Color normalUnderline, activeUndeline, hoverUnderline; - private boolean isActive; - private boolean cursorInControl; - private Rectangle cachedClientArea; - private Listener shellListener; - private Shell shell; - private int mnemonic = -1; - - - /** - * Constructs a new instance of this class given its parent - * and a style value describing its behavior and appearance. - *

- * The style value is either one of the style constants defined in - * class SWT which is applicable to instances of this - * class, or must be built by bitwise OR'ing together - * (that is, using the int "|" operator) two or more - * of those SWT style constants. The class description - * lists the style constants that are applicable to the class. - * Style bits are also inherited from superclasses. - *

- * - * @param parent a widget which will be the parent of the new instance (cannot be null) - * @param style the style of widget to construct - * - * @exception IllegalArgumentException
    - *
  • ERROR_NULL_ARGUMENT - if the parent is null
  • - *
- * @exception SWTException
    - *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent
  • - *
- */ - - public Hyperlink(Composite parent, int style) - { - super(parent, checkStyle(style)); - - handCursor = new Cursor(getDisplay(), SWT.CURSOR_HAND); - arrowCursor = new Cursor(getDisplay(), SWT.CURSOR_ARROW); - setCursor(handCursor); - - normalForeground = getDisplay().getSystemColor(SWT.COLOR_BLUE); - hoverForeground = normalForeground; - activeForeground = getDisplay().getSystemColor(SWT.COLOR_RED); - - normalUnderline = null; - hoverUnderline = normalForeground; - activeUndeline = activeForeground; - - super.setForeground(normalForeground); - - addPaintListener(new PaintListener() - { - @Override - public void paintControl(PaintEvent event) - { - onPaint(event); - } - }); - - addDisposeListener(new DisposeListener() - { - @Override - public void widgetDisposed(DisposeEvent e) - { - if(handCursor != null) - { - handCursor.dispose(); - handCursor = null; - } - if(arrowCursor != null) - { - arrowCursor.dispose(); - arrowCursor = null; - } - if(shellListener != null) - { - shell.removeListener(SWT.Activate, shellListener); - shell.removeListener(SWT.Deactivate, shellListener); - shellListener = null; - } - text = null; - } - }); - - addListener(SWT.MouseDown, new Listener() - { - @Override - public void handleEvent(Event event) - { - isActive = true; - cursorInControl = true; - redraw(); - } - }); - - addListener(SWT.MouseUp, new Listener() - { - @Override - public void handleEvent(Event event) - { - isActive = false; - redraw(); - if(cursorInControl) linkActivated(); - } - }); - - addListener(SWT.Resize, new Listener() - { - @Override - public void handleEvent(Event event) - { - cachedClientArea = getClientArea(); - } - }); - - Listener mouseListener = new Listener() - { - @Override - public void handleEvent(Event event) - { - boolean newCursorInControl = isInClientArea(event); - if(cursorInControl != newCursorInControl) - { - cursorInControl = newCursorInControl; - if(cursorInControl) setCursor(handCursor); - else if(isActive) setCursor(arrowCursor); - if(isActive || (normalForeground != hoverForeground) || (normalUnderline != hoverUnderline)) redraw(); - } - } - }; - addListener(SWT.MouseMove, mouseListener); - addListener(SWT.MouseEnter, mouseListener); - addListener(SWT.MouseExit, mouseListener); - - cachedClientArea = getClientArea(); - - if((style & SWT.NO_FOCUS) == 0) // Take focus - { - addListener(SWT.KeyDown, new Listener() - { - @Override - public void handleEvent(Event event) - { - if(event.character == ' ') linkActivated(); - } - }); - - addListener(SWT.Traverse, new Listener() - { - @Override - public void handleEvent(Event event) - { - if(event.detail == SWT.TRAVERSE_RETURN) - { - linkActivated(); - event.doit = false; - } - else if(event.detail == SWT.TRAVERSE_MNEMONIC) - { - if(mnemonic != -1 && Character.toLowerCase(event.character) == mnemonic) - { - setFocus(); - linkActivated(); - event.doit = false; - } - else event.doit = true; - } - else event.doit = true; // Accept all other traversal keys - } - }); - - addListener(SWT.FocusIn, new Listener() - { - @Override - public void handleEvent(Event event) - { - //System.out.println("FocusIn"); - redraw(); - } - }); - - addListener(SWT.FocusOut, new Listener() - { - @Override - public void handleEvent(Event event) - { - //System.out.println("FocusOut"); - redraw(); - } - }); - } - else // Don't take focus but still support mnemonics - { - addListener(SWT.Traverse, new Listener() - { - @Override - public void handleEvent(Event event) - { - if(event.detail == SWT.TRAVERSE_MNEMONIC && mnemonic != -1 && Character.toLowerCase(event.character) == mnemonic) - { - linkActivated(); - event.doit = false; - } - } - }); - } - - Composite shellComp = getParent(); - while(shellComp != null && (!(shellComp instanceof Shell))) shellComp = shellComp.getParent(); - shell = (Shell)shellComp; - - if(shell != null) - { - shellListener = new Listener() // Remove stale mouse hover on shell activation / deactivation - { - @Override - public void handleEvent(Event event) - { - boolean newCursorInControl = getDisplay().getCursorControl() == Hyperlink.this; - //System.out.println("Shell (de)activated. Cursor over control: "+newCursorInControl); - if(cursorInControl != newCursorInControl) - { - cursorInControl = newCursorInControl; - if(cursorInControl) setCursor(handCursor); - else if(isActive) setCursor(arrowCursor); - if(isActive || (normalForeground != hoverForeground) || (normalUnderline != hoverUnderline)) redraw(); - } - } - }; - - shell.addListener(SWT.Activate, shellListener); - shell.addListener(SWT.Deactivate, shellListener); - } - } - - - private void linkActivated() - { - //System.out.println("Link clicked!"); - Event e = new Event(); - e.widget = this; - e.type = SWT.Selection; - notifyListeners(SWT.Selection, e); - } - - - private boolean isInClientArea(Event event) - { - return event.x >= cachedClientArea.x && event.x < cachedClientArea.x+cachedClientArea.width && - event.y >= cachedClientArea.y && event.y < cachedClientArea.y+cachedClientArea.height; - } - - - @Override - public boolean isReparentable () - { - checkWidget (); - return false; - } - - - /** - * Check the style bits to ensure that no invalid styles are applied. - */ - - private static int checkStyle(int style) - { - style = style & SWT.NO_FOCUS; - - // [NOTE] The following transparency workaround was taken from CLabel - //TEMPORARY CODE - /* - * The default background on carbon and some GTK themes is not a solid color - * but a texture. To show the correct default background, we must allow - * the operating system to draw it and therefore, we can not use the - * NO_BACKGROUND style. The NO_BACKGROUND style is not required on platforms - * that use double buffering which is true in both of these cases. - */ - String platform = SWT.getPlatform(); - if ("carbon".equals(platform) || "gtk".equals(platform)) return style; - return style | SWT.NO_BACKGROUND; - } - - - @Override - public Point computeSize(int wHint, int hHint, boolean changed) - { - checkWidget(); - Point e = getTotalSize(text); - if (wHint != SWT.DEFAULT) e.x = wHint; - if (hHint != SWT.DEFAULT) e.y = hHint; - return e; - } - - - /** - * Compute the minimum size. - */ - - private Point getTotalSize(String text) - { - Point size = new Point(0, 0); - GC gc = new GC(this); - - if (text != null && text.length() > 0) - { - Point e = gc.textExtent(text, SWT.DRAW_MNEMONIC); - size.x += e.x; - size.y = Math.max(size.y, e.y); - } - else size.y = Math.max(size.y, gc.getFontMetrics().getHeight()); - - gc.dispose(); - return size; - } - - - /** - * Return the Hyperlink's displayed text. - * - * @return the text of the hyperlink or null - */ - - public String getText() - { - return text; - } - - - private void onPaint(PaintEvent event) - { - Rectangle rect = cachedClientArea; // getClientArea(); - if (rect.width == 0 || rect.height == 0) return; - - Point extent = getTotalSize(text); - - GC gc = event.gc; - - if ((getStyle() & SWT.NO_BACKGROUND) != 0) - { - gc.setBackground(getBackground()); - gc.fillRectangle(rect); - } - - if(isFocusControl()) gc.drawFocus(rect.x, rect.y, rect.width, rect.height); - - Color textFG, lineFG; - - if(cursorInControl) - { - textFG = isActive ? activeForeground : hoverForeground; - lineFG = isActive ? activeUndeline : hoverUnderline; - } - else - { - textFG = /* isActive ? mouseOverForeground : */ normalForeground; - lineFG = /* isActive ? mouseOverUnderline : */ normalUnderline; - } - - if(textFG == null) textFG = normalForeground; - if(textFG == null) textFG = getDisplay().getSystemColor(SWT.COLOR_WIDGET_FOREGROUND); - - int textHeight = gc.getFontMetrics().getHeight(); - - gc.setForeground(textFG); - gc.drawText(text, rect.x, rect.y + (rect.height - textHeight) / 2, SWT.DRAW_TRANSPARENT | SWT.DRAW_MNEMONIC); - - int uy = (rect.y + (rect.height - textHeight) / 2) + gc.getFontMetrics().getAscent() + gc.getFontMetrics().getLeading() + 1; - int lineWidth = extent.x > rect.width ? rect.width : extent.x; - - if(lineFG != null) - { - if(lineFG != textFG) gc.setForeground(lineFG); - gc.drawLine(rect.x, uy, rect.x + lineWidth, uy); - } - } - - - @Override - public void setForeground(Color color) - { - super.setForeground(color); - this.normalForeground = color; - redraw(); - } - - - public void setHoverForeground(Color color) - { - this.hoverForeground = color; - redraw(); - } - - - public void setActiveForeground(Color color) - { - this.activeForeground = color; - redraw(); - } - - - public void setUnderline(Color color) - { - this.normalUnderline = color; - redraw(); - } - - - public void setHoverUnderline(Color color) - { - this.hoverUnderline = color; - redraw(); - } - - - public void setActiveUnderline(Color color) - { - this.activeUndeline = color; - redraw(); - } - - - public Color getHoverForeground() - { - return this.hoverForeground; - } - - - public Color getActiveForeground() - { - return this.activeForeground; - } - - - public Color getUnderline() - { - return this.normalUnderline; - } - - - public Color getHoverUnderline() - { - return this.hoverUnderline; - } - - - public Color getActiveUnderline() - { - return this.activeUndeline; - } - - - @Override - public void setBackground(Color color) - { - super.setBackground(color); - redraw(); - } - - - @Override - public void setFont(Font font) - { - super.setFont(font); - redraw(); - } - - - /** - * Set the Hyperlink's displayed text. - * The value null clears it. - *

- * Mnemonics are indicated by an '&' that causes the next - * character to be the mnemonic. When the user presses a - * key sequence that matches the mnemonic, a selection - * event occurs. On most platforms, the mnemonic appears - * underlined but may be emphasised in a platform specific - * manner. The mnemonic indicator character '&' can be - * escaped by doubling it in the string, causing a single - * '&' to be displayed. - *

- * - * @param text the text to be displayed in the hyperlink or null - * - * @exception SWTException
    - *
  • ERROR_WIDGET_DISPOSED - if the receiver has been disposed
  • - *
  • ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver
  • - *
- */ - - public void setText(String text) - { - checkWidget(); - if (text == null) text = ""; - if (!text.equals(this.text)) - { - this.text = text; - int i = text.indexOf('&'); - if(i == -1 || i == text.length()-1) mnemonic = -1; - else mnemonic = Character.toLowerCase(text.charAt(i+1)); - redraw(); - } - } - - - /** - * Adds the listener to receive events. - * - * @param listener the listener - * - * @exception SWTError(ERROR_THREAD_INVALID_ACCESS) - * when called from the wrong thread - * @exception SWTError(ERROR_WIDGET_DISPOSED) - * when the widget has been disposed - * @exception SWTError(ERROR_NULL_ARGUMENT) - * when listener is null - */ - - public void addSelectionListener(SelectionListener listener) - { - checkWidget(); - if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); - TypedListener typedListener = new TypedListener(listener); - addListener(SWT.Selection, typedListener); - addListener(SWT.DefaultSelection, typedListener); - } - - - /** - * Removes the listener. - * - * @param listener the listener - * - * @exception SWTError(ERROR_THREAD_INVALID_ACCESS) - * when called from the wrong thread - * @exception SWTError(ERROR_WIDGET_DISPOSED) - * when the widget has been disposed - * @exception SWTError(ERROR_NULL_ARGUMENT) - * when listener is null - */ - - public void removeSelectionListener(SelectionListener listener) - { - checkWidget(); - if (listener == null) SWT.error(SWT.ERROR_NULL_ARGUMENT); - removeListener(SWT.Selection, listener); - removeListener(SWT.DefaultSelection, listener); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/HyperlinkDialog.java b/src/main/java/com/salesforce/dataloader/ui/HyperlinkDialog.java index b0350e742..6f5749078 100644 --- a/src/main/java/com/salesforce/dataloader/ui/HyperlinkDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/HyperlinkDialog.java @@ -26,12 +26,6 @@ package com.salesforce.dataloader.ui; -import java.awt.Desktop; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; - -import org.apache.log4j.Logger; import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.resource.JFaceColors; import org.eclipse.jface.resource.JFaceResources; @@ -42,28 +36,13 @@ import org.eclipse.swt.widgets.*; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.util.StreamGobbler; -public class HyperlinkDialog extends Dialog { +public class HyperlinkDialog extends BaseDialog { private String boldMessage; - private String message; - private String linkText; - private String linkURL; - - public String getLinkURL() { - return linkURL; - } - - public void setLinkURL(String linkURL) { - this.linkURL = linkURL; - } - - private Logger logger = Logger.getLogger(HyperlinkDialog.class); - private Text messageLabel; private Label titleLabel; private Label titleImage; private Label titleBanner; - private Hyperlink link; + private Link link; /** * InputDialog constructor @@ -72,45 +51,7 @@ public void setLinkURL(String linkURL) { * the parent */ public HyperlinkDialog(Shell parent, Controller controller) { - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public HyperlinkDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public boolean open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the sucess - return true; + super(parent, controller); } /** @@ -119,7 +60,7 @@ public boolean open() { * @param shell * the dialog window */ - private void createContents(final Shell shell) { + protected void createContents(final Shell shell) { FormLayout layout = new FormLayout(); shell.setLayout(layout); @@ -160,76 +101,24 @@ private void createContents(final Shell shell) { titleData.left = new FormAttachment(0, 10); titleLabel.setLayoutData(titleData); - messageLabel = new Text(shell, SWT.WRAP | SWT.READ_ONLY); - messageLabel.setForeground(foreground); - messageLabel.setBackground(background); - messageLabel.setText(message); // two lines - messageLabel.setFont(JFaceResources.getDialogFont()); + link = new Link(shell, SWT.WRAP | SWT.READ_ONLY); + link.setForeground(foreground); + link.setBackground(background); + link.setText(this.getMessage()); // two lines + link.setFont(JFaceResources.getDialogFont()); FormData messageLabelData = new FormData(); messageLabelData.top = new FormAttachment(titleLabel, 10); messageLabelData.right = new FormAttachment(titleImage); messageLabelData.left = new FormAttachment(0, 10); // messageLabelData.bottom = new FormAttachment(titleImage, 0, SWT.BOTTOM); - messageLabel.setLayoutData(messageLabelData); - - link = new Hyperlink(shell, SWT.NONE); - link.setText(getLinkText()); - link.setBackground(background); - link.addSelectionListener(new SelectionListener() { + link.setLayoutData(messageLabelData); + link.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent e) { - Thread runner = new Thread() { - @Override - public void run() { - int exitVal = 0; - try { - Process proc = null; - if (System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) - proc = Runtime.getRuntime().exec("rundll32 url.dll, FileProtocolHandler " + getLinkURL()); - else if (System.getProperty("os.name").toLowerCase().indexOf("mac") >= 0){ - Desktop desktop = Desktop.getDesktop(); - try { - desktop.browse(new URI(getLinkURL())); - } catch (URISyntaxException e) { - // TODO Auto-generated catch block - logger.error("Browser Error"); - } - } - else { - logger.error("OS is not supported."); - return; - } - - StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "ERROR"); //$NON-NLS-1$ - StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "OUTPUT"); //$NON-NLS-1$ - errorGobbler.start(); - outputGobbler.start(); - exitVal = proc.waitFor(); - } catch (IOException iox) { - logger.error("Browser Error", iox); - } catch (InterruptedException ie) { - logger.error("Browser Error", ie); - } - - if (exitVal != 0) { - logger.error("Process exited with error" + exitVal); - } - } - }; - - runner.setPriority(Thread.MAX_PRIORITY); - runner.start(); + URLUtil.openURL(e.text); } - - @Override - public void widgetDefaultSelected(SelectionEvent e) {} }); - FormData linkData = new FormData(); - linkData.top = new FormAttachment(messageLabel); - linkData.right = new FormAttachment(titleImage); - linkData.left = new FormAttachment(0, 10); - link.setLayoutData(linkData); - + Composite greyArea = new Composite(shell, SWT.NULL); GridLayout childLayout = new GridLayout(1, false); childLayout.marginHeight = 0; @@ -273,14 +162,6 @@ public void widgetSelected(SelectionEvent event) { } - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - public String getBoldMessage() { return boldMessage; } @@ -288,12 +169,4 @@ public String getBoldMessage() { public void setBoldMessage(String message) { boldMessage = message; } - - public String getLinkText() { - return linkText; - } - - public void setLinkText(String linkText) { - this.linkText = linkText; - } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/Labels.java b/src/main/java/com/salesforce/dataloader/ui/Labels.java index d8901791e..1c7ee4e1b 100644 --- a/src/main/java/com/salesforce/dataloader/ui/Labels.java +++ b/src/main/java/com/salesforce/dataloader/ui/Labels.java @@ -26,6 +26,7 @@ package com.salesforce.dataloader.ui; import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; import java.text.MessageFormat; import java.util.MissingResourceException; import java.util.ResourceBundle; @@ -46,7 +47,7 @@ private Labels() {} public static String getString(String key) { try { //Hmm.. need to test other languages on this at some point - return new String(RESOURCE_BUNDLE.getString(key).getBytes("ISO-8859-1"), "UTF-8");//$NON-NLS-2$ //$NON-NLS-1$ + return new String(RESOURCE_BUNDLE.getString(key).getBytes("ISO-8859-1"), StandardCharsets.UTF_8.name());//$NON-NLS-2$ //$NON-NLS-1$ } catch (MissingResourceException e) { return '!' + key + '!'; } catch (UnsupportedEncodingException e) { diff --git a/src/main/java/com/salesforce/dataloader/ui/LoadFinishDialog.java b/src/main/java/com/salesforce/dataloader/ui/LoadFinishDialog.java index 99e610334..0225c3249 100644 --- a/src/main/java/com/salesforce/dataloader/ui/LoadFinishDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/LoadFinishDialog.java @@ -32,14 +32,12 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; -public class LoadFinishDialog extends Dialog { - private String message; +public class LoadFinishDialog extends WizardDialog { private Label label; - private Controller controller; private Button ok; /** @@ -49,65 +47,7 @@ public class LoadFinishDialog extends Dialog { * the parent */ public LoadFinishDialog(Shell parent, Controller controller) { - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - this.controller = controller; - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public LoadFinishDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - setText(Labels.getString("LoadFinishDialog.title")); //$NON-NLS-1$ - } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; - } - - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public boolean open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the sucess - return true; + super(parent, controller); } /** @@ -116,7 +56,7 @@ public boolean open() { * @param shell * the dialog window */ - private void createContents(final Shell shell) { + protected void createContents(final Shell shell) { GridData data; @@ -131,7 +71,7 @@ private void createContents(final Shell shell) { labelInfo.setLayoutData(data); label = new Label(shell, SWT.NONE); - label.setText(message); + label.setText(getMessage()); data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING); label.setLayoutData(data); @@ -156,7 +96,7 @@ private void createContents(final Shell shell) { viewSuccess.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - openViewer(controller.getConfig().getString(Config.OUTPUT_SUCCESS)); + openViewer(getController().getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS)); } @Override @@ -169,7 +109,7 @@ public void widgetDefaultSelected(SelectionEvent e) {} viewErrors.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - openViewer(controller.getConfig().getString(Config.OUTPUT_ERROR)); + openViewer(getController().getAppConfig().getString(AppConfig.PROP_OUTPUT_ERROR)); } @Override @@ -193,10 +133,9 @@ public void widgetSelected(SelectionEvent event) { } private void openViewer(String filename) { - CSVViewerDialog dlg = new CSVViewerDialog(getParent(), controller); + CSVViewerDialog dlg = new CSVViewerDialog(getParent(), getController(), false, false); dlg.setNumberOfRows(200000); dlg.setFileName(filename); - dlg.setUseCustomSplitter(false); try { dlg.open(); } catch (DataAccessObjectInitializationException e) { diff --git a/src/main/java/com/salesforce/dataloader/ui/LoadPage.java b/src/main/java/com/salesforce/dataloader/ui/LoadPage.java index e20f61906..2f08cee12 100644 --- a/src/main/java/com/salesforce/dataloader/ui/LoadPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/LoadPage.java @@ -25,8 +25,8 @@ */ package com.salesforce.dataloader.ui; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.wizard.WizardPage; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; /** * This is the base class for the LoadWizard ui pages. Allows navigation to be done dynamically by forcing setupPage to @@ -35,7 +35,7 @@ * @author Alex Warshavsky * @since 8.0 */ -public abstract class LoadPage extends WizardPage { +public abstract class LoadPage extends OperationPage { /** * @param pageName @@ -43,11 +43,22 @@ public abstract class LoadPage extends WizardPage { * @param titleImage * */ - public LoadPage(String pageName, String title, ImageDescriptor titleImage) { - super(pageName, title, titleImage); - } - abstract boolean setupPage(); + + public LoadPage(String name, Controller controller) { + super(name, controller); + } + + @Override + protected String getConfigInfo() { + return Labels.getString("LoadPage.importBatchSize") + + " " + + controller.getAppConfig().getMaxRowsInImportBatch() + + " " + + Labels.getString("AdvancedSettingsDialog.uiLabel." + AppConfig.PROP_LOAD_ROW_TO_START_AT) + + " " + + controller.getAppConfig().getString(AppConfig.PROP_LOAD_ROW_TO_START_AT); //$NON-NLS-1$ + } /* * Common code for getting the next page @@ -55,18 +66,10 @@ public LoadPage(String pageName, String title, ImageDescriptor titleImage) { @Override public LoadPage getNextPage() { LoadPage nextPage = (LoadPage)super.getNextPage(); - if(nextPage != null && nextPage.setupPage()) { + if( nextPage != null && nextPage.setupPage()) { return nextPage; } else { return this; } } - - /** - * Need to subclass this function to prevent the getNextPage() function being called before the button is clicked. - */ - @Override - public boolean canFlipToNextPage() { - return isPageComplete(); - } } diff --git a/src/main/java/com/salesforce/dataloader/ui/LoadWizard.java b/src/main/java/com/salesforce/dataloader/ui/LoadWizard.java index 78938049f..e863af8d4 100644 --- a/src/main/java/com/salesforce/dataloader/ui/LoadWizard.java +++ b/src/main/java/com/salesforce/dataloader/ui/LoadWizard.java @@ -30,11 +30,13 @@ import java.io.File; import java.lang.reflect.InvocationTargetException; -import org.apache.log4j.Logger; -import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.swt.SWT; import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.ProcessInitializationException; @@ -43,7 +45,7 @@ */ public abstract class LoadWizard extends BaseWizard { - private static final Logger logger = Logger.getLogger(LoadWizard.class); + private static final Logger logger = DLLogManager.getLogger(LoadWizard.class); /** * LoadWizard constructor @@ -73,7 +75,7 @@ public boolean performFinish() { String outputDirName = getFinishPage().getOutputDir(); File statusDir = new File(outputDirName); if (!statusDir.exists() || !statusDir.isDirectory()) { - UIUtils.errorMessageBox(getShell(), Labels.getString("LoadWizard.errorValidDirectory")); //$NON-NLS-1$ + UIUtils.errorMessageBox(getShell(), Labels.getString("LoadWizard.errorValidFolder")); //$NON-NLS-1$ return false; } // set the files for status output @@ -92,7 +94,7 @@ public boolean performFinish() { if (!wizardhook_validateFinish()) { return false; } try { - ProgressMonitorDialog dlg = new ProgressMonitorDialog(getShell()); + DLProgressMonitorDialog dlg = new DLProgressMonitorDialog(getShell()); dlg.run(true, true, new SWTLoadRunable(getController())); } catch (InvocationTargetException e) { @@ -105,7 +107,7 @@ public boolean performFinish() { return false; } - return true; + return closeWizardPagePostSuccessfulFinish(); } @Override @@ -124,8 +126,8 @@ protected FinishPage setPages() { } @Override - protected SettingsPage createSettingsPage() { - return new SettingsPage(getController()); + protected LoginPage createLoginPage() { + return new LoginPage(getController()); } protected FinishPage createFinishPage() { @@ -140,7 +142,9 @@ protected FinishPage getFinishPage() { protected void hook_additionalLoadWizardPages() {} private String getConfirmationText() { - return getLabel("confFirstLine") + System.getProperty("line.separator") //$NON-NLS-1$ //$NON-NLS-2$ + return getLabel("confFirstLine") + + System.getProperty("line.separator") //$NON-NLS-1$ //$NON-NLS-2$ + + System.getProperty("line.separator") //$NON-NLS-1$ //$NON-NLS-2$ + getLabel("confSecondLine"); //$NON-NLS-1$ } @@ -148,7 +152,17 @@ public static final class DeleteWizard extends LoadWizard { public DeleteWizard(Controller controller) { super(controller, OperationInfo.delete); } - + + @Override + protected void hook_additionalLoadWizardPages() { + super.hook_additionalLoadWizardPages(); + if (getController().getAppConfig().isRESTAPIEnabled() + && Controller.getAPIMajorVersion() >= 61 + && getController().getAppConfig().getBoolean(AppConfig.PROP_DELETE_WITH_EXTERNALID)) { + addPage(new ExternalIdPage(getController())); + } + } + @Override public boolean wizardhook_validateFinish() { int button = UIUtils.warningConfMessageBox(getShell(), getLabel("validateFirstLine") //$NON-NLS-1$ @@ -156,6 +170,12 @@ public boolean wizardhook_validateFinish() { return button == SWT.YES; } } + + public static final class UndeleteWizard extends LoadWizard { + public UndeleteWizard(Controller controller) { + super(controller, OperationInfo.undelete); + } + } public static final class HardDeleteWizard extends LoadWizard { public HardDeleteWizard(Controller controller) { @@ -172,6 +192,16 @@ public static final class UpdateWizard extends LoadWizard { public UpdateWizard(Controller controller) { super(controller, OperationInfo.update); } + + @Override + protected void hook_additionalLoadWizardPages() { + super.hook_additionalLoadWizardPages(); + if (getController().getAppConfig().isRESTAPIEnabled() + && Controller.getAPIMajorVersion() >= 61) { + addPage(new ExternalIdPage(getController())); + } + addPage(new ChooseLookupFieldForRelationshipPage(getController())); + } } public static final class UpsertWizard extends LoadWizard { @@ -183,7 +213,7 @@ public UpsertWizard(Controller controller) { protected void hook_additionalLoadWizardPages() { super.hook_additionalLoadWizardPages(); addPage(new ExternalIdPage(getController())); - addPage(new ForeignKeyExternalIdPage(getController())); + addPage(new ChooseLookupFieldForRelationshipPage(getController())); } } @@ -191,6 +221,12 @@ public static final class InsertWizard extends LoadWizard { public InsertWizard(Controller controller) { super(controller, OperationInfo.insert); } + + @Override + protected void hook_additionalLoadWizardPages() { + super.hook_additionalLoadWizardPages(); + addPage(new ChooseLookupFieldForRelationshipPage(getController())); + } } } diff --git a/src/main/java/com/salesforce/dataloader/ui/LoaderTitleAreaDialog.java b/src/main/java/com/salesforce/dataloader/ui/LoaderTitleAreaDialog.java index ed246c09d..2a4752c83 100644 --- a/src/main/java/com/salesforce/dataloader/ui/LoaderTitleAreaDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/LoaderTitleAreaDialog.java @@ -31,6 +31,8 @@ import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.*; import org.eclipse.swt.layout.*; import org.eclipse.swt.widgets.*; @@ -83,7 +85,7 @@ public static Image getImageFromRegistry(String imgKey) { Color titleAreaColor; private String message = ""; //$NON-NLS-1$ private String errorMessage; - private Text messageLabel; + private Link messageLink; private Composite workArea; private Label messageImageLabel; private Image messageImage; @@ -233,10 +235,10 @@ public void widgetDisposed(DisposeEvent e) { messageImageLabel.setBackground(background); // Message label @ bottom, center - messageLabel = new Text(parent, SWT.WRAP | SWT.READ_ONLY); - JFaceColors.setColors(messageLabel, foreground, background); - messageLabel.setText(" \n "); // two lines//$NON-NLS-1$ - messageLabel.setFont(JFaceResources.getDialogFont()); + messageLink = new Link(parent, SWT.WRAP | SWT.READ_ONLY); + JFaceColors.setColors(messageLink, foreground, background); + messageLink.setText(" \n "); // two lines//$NON-NLS-1$ + messageLink.setFont(JFaceResources.getDialogFont()); // Filler labels leftFillerLabel = new Label(parent, SWT.CENTER); leftFillerLabel.setBackground(background); @@ -246,7 +248,7 @@ public void widgetDisposed(DisposeEvent e) { determineTitleImageLargest(); if (titleImageLargest) return titleImage; - return messageLabel; + return messageLink; } /** * Determine if the title image is larger than the title message and message @@ -255,13 +257,13 @@ public void widgetDisposed(DisposeEvent e) { private void determineTitleImageLargest() { int titleY = titleImage.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; int labelY = titleLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; - labelY += messageLabel.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; - FontData[] data = messageLabel.getFont().getFontData(); + labelY += messageLink.computeSize(SWT.DEFAULT, SWT.DEFAULT).y; + FontData[] data = messageLink.getFont().getFontData(); labelY += data[0].getHeight(); titleImageLargest = titleY > labelY; } /** - * Set the layout values for the messageLabel, messageImageLabel and + * Set the layout values for the messageLink, messageImageLabel and * fillerLabel for the case where there is a normal message. * * @param verticalSpacing @@ -275,19 +277,19 @@ private void setLayoutsForNormalMessage(int verticalSpacing, messageImageData.top = new FormAttachment(titleLabel, verticalSpacing); messageImageData.left = new FormAttachment(0, H_GAP_IMAGE); messageImageLabel.setLayoutData(messageImageData); - FormData messageLabelData = new FormData(); - messageLabelData.top = new FormAttachment(titleLabel, verticalSpacing); - messageLabelData.right = new FormAttachment(titleImage); - messageLabelData.left = new FormAttachment(messageImageLabel, + FormData messageLinkData = new FormData(); + messageLinkData.top = new FormAttachment(titleLabel, verticalSpacing); + messageLinkData.right = new FormAttachment(titleImage); + messageLinkData.left = new FormAttachment(messageImageLabel, horizontalSpacing); if (titleImageLargest) - messageLabelData.bottom = new FormAttachment(titleImage, 0, + messageLinkData.bottom = new FormAttachment(titleImage, 0, SWT.BOTTOM); - messageLabel.setLayoutData(messageLabelData); + messageLink.setLayoutData(messageLinkData); FormData fillerData = new FormData(); fillerData.left = new FormAttachment(0, horizontalSpacing); fillerData.top = new FormAttachment(messageImageLabel, 0); - fillerData.bottom = new FormAttachment(messageLabel, 0, SWT.BOTTOM); + fillerData.bottom = new FormAttachment(messageLink, 0, SWT.BOTTOM); bottomFillerLabel.setLayoutData(fillerData); FormData data = new FormData(); data.top = new FormAttachment(messageImageLabel, 0, SWT.TOP); @@ -348,24 +350,24 @@ public void setErrorMessage(String newErrorMessage) { updateMessage(message); messageImageLabel.setImage(messageImage); setImageLabelVisible(messageImage != null); - messageLabel.setToolTipText(message); + messageLink.setToolTipText(message); } else { //Add in a space for layout purposes but do not //change the instance variable String displayedErrorMessage = " " + errorMessage; //$NON-NLS-1$ updateMessage(displayedErrorMessage); - messageLabel.setToolTipText(errorMessage); + messageLink.setToolTipText(errorMessage); if (!showingError) { // we were not previously showing an error showingError = true; // lazy initialize the error background color and image if (errorMsgAreaBackground == null) { errorMsgAreaBackground = JFaceColors - .getErrorBackground(messageLabel.getDisplay()); + .getErrorBackground(messageLink.getDisplay()); errorMsgImage = getImageFromRegistry(DLG_IMG_TITLE_ERROR); } // show the error - normalMsgAreaBackground = messageLabel.getBackground(); + normalMsgAreaBackground = messageLink.getBackground(); setMessageBackgrounds(true); messageImageLabel.setImage(errorMsgImage); setImageLabelVisible(true); @@ -400,7 +402,7 @@ private void layoutForNewMessage() { data = new FormData(); data.top = new FormAttachment(messageImageLabel, 0); data.left = new FormAttachment(0, 0); - data.bottom = new FormAttachment(messageLabel, 0, SWT.BOTTOM); + data.bottom = new FormAttachment(messageLink, 0, SWT.BOTTOM); data.right = new FormAttachment(messageImageLabel, 0, SWT.RIGHT); bottomFillerLabel.setLayoutData(data); data = new FormData(); @@ -409,15 +411,15 @@ private void layoutForNewMessage() { data.bottom = new FormAttachment(messageImageLabel, 0, SWT.BOTTOM); data.right = new FormAttachment(messageImageLabel, 0); leftFillerLabel.setLayoutData(data); - FormData messageLabelData = new FormData(); - messageLabelData.top = new FormAttachment(titleLabel, + FormData messageLinkData = new FormData(); + messageLinkData.top = new FormAttachment(titleLabel, verticalSpacing); - messageLabelData.right = new FormAttachment(titleImage); - messageLabelData.left = new FormAttachment(messageImageLabel, 0); + messageLinkData.right = new FormAttachment(titleImage); + messageLinkData.left = new FormAttachment(messageImageLabel, 0); if (titleImageLargest) - messageLabelData.bottom = new FormAttachment(titleImage, 0, + messageLinkData.bottom = new FormAttachment(titleImage, 0, SWT.BOTTOM); - messageLabel.setLayoutData(messageLabelData); + messageLink.setLayoutData(messageLinkData); } //Do not layout before the dialog area has been created //to avoid incomplete calculations. @@ -502,21 +504,35 @@ private void showMessage(String newMessage, Image newImage) { updateMessage(shownMessage); messageImageLabel.setImage(messageImage); setImageLabelVisible(messageImage != null); - messageLabel.setToolTipText(message); + messageLink.setToolTipText(message); layoutForNewMessage(); } } /** - * Update the contents of the messageLabel. + * Update the contents of the messageLink. * * @param newMessage * the message to use */ private void updateMessage(String newMessage) { - //Be sure there are always 2 lines for layout purposes - if (newMessage != null && newMessage.indexOf('\n') == -1) - newMessage = newMessage + "\n "; //$NON-NLS-1$ - messageLabel.setText(newMessage); + //Be sure there are always 4 lines for layout purposes + if (newMessage == null) { + newMessage = ""; + } + String[] parts = newMessage.split("\n"); + if (parts.length < 4) { + for (int i = 0; i < 4 - parts.length; i++) { + newMessage = newMessage + "\n "; //$NON-NLS-1$ + } + } + messageLink.setText(newMessage); + messageLink.redraw(); + messageLink.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + URLUtil.openURL(e.text); + } + }); } /** * Sets the title to be shown in the title area of this dialog. @@ -556,7 +572,7 @@ public void setTitleImage(Image newTitleImage) { if (titleImageLargest) top = titleImage; else - top = messageLabel; + top = messageLink; resetWorkAreaAttachments(top); } } @@ -576,7 +592,7 @@ public void setBannerImage(Image newBannerImage) { if (titleImageLargest) top = titleImage; else - top = messageLabel; + top = messageLink; resetWorkAreaAttachments(top); } } @@ -605,7 +621,7 @@ private void setMessageBackgrounds(boolean showingError) { color = errorMsgAreaBackground; else color = normalMsgAreaBackground; - messageLabel.setBackground(color); + messageLink.setBackground(color); messageImageLabel.setBackground(color); bottomFillerLabel.setBackground(color); leftFillerLabel.setBackground(color); diff --git a/src/main/java/com/salesforce/dataloader/ui/LoaderTitleDialog.java b/src/main/java/com/salesforce/dataloader/ui/LoaderTitleDialog.java deleted file mode 100644 index 20e12dfb3..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/LoaderTitleDialog.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui; - -import org.eclipse.jface.dialogs.IDialogConstants; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.*; - -import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; - -/** - * Splash screen for the loader. - * - * @author Lexi Viripaeff - * @since 6.0 - */ -public class LoaderTitleDialog extends LoaderTitleAreaDialog { - - private final Config config; - - /** - * MyTitleAreaDialog constructor - * - * @param shell - * the parent shell - */ - public LoaderTitleDialog(Shell activeShell, Config cfg) { - super(activeShell); - this.config = cfg; - } - - /** - * Creates the dialog's contents - * - * @param parent - * the parent composite - * @return Control - */ - @Override - protected Control createContents(Composite parent) { - Control contents = super.createContents(parent); - - // Set the title - setTitle(Labels.getString("TitleDialog.title")); //$NON-NLS-1$ - - // Set the message - setMessage(Labels.getString("TitleDialog.messageLineOne") //$NON-NLS-1$ - + System.getProperty("line.separator") + Labels.getString("TitleDialog.messageLineTwo")); //$NON-NLS-1$ //$NON-NLS-2$ - - // Set the image - setTitleImage(UIUtils.getImageRegistry().get("splashscreens")); //$NON-NLS-1$ - - return contents; - } - - /** - * Creates the gray area - * - * @param parent - * the parent composite - * @return Control - */ - @Override - protected Control createDialogArea(Composite parent) { - Composite composite = (Composite)super.createDialogArea(parent); - - return composite; - } - - /** - * Creates the buttons for the button bar - * - * @param parent - * the parent composite - */ - @Override - protected void createButtonsForButtonBar(Composite parent) { - // create all the buttons, in order - for (OperationInfo info : OperationInfo.ALL_OPERATIONS_IN_ORDER) { - final Button butt = createButton(parent, info.getDialogIdx(), info.getLabel(), false); - butt.setEnabled(info.isOperationAllowed(this.config)); - Image img = info.getIconImage(); - butt.setImage(img); - GridData gd = (GridData)butt.getLayoutData(); - gd.grabExcessHorizontalSpace = true; - gd.widthHint += img.getImageData().width; - butt.setLayoutData(gd); - } - createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true); - } - - /** - * Sets the button behavior - */ - @Override - protected void buttonPressed(int buttonID) { - setReturnCode(buttonID); - close(); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/LoaderUpgradeDialog.java b/src/main/java/com/salesforce/dataloader/ui/LoaderUpgradeDialog.java new file mode 100644 index 000000000..d2347b603 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/LoaderUpgradeDialog.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.util.AppUtil; + +/** + * Upgrade dialog for the loader. + * + */ +public class LoaderUpgradeDialog extends LoaderTitleAreaDialog { + + private Controller controller; + /** + * MyTitleAreaDialog constructor + * + * @param shell + * the parent shell + */ + public LoaderUpgradeDialog(Shell activeShell, Controller controller) { + super(activeShell); + this.controller = controller; + } + + /** + * Creates the dialog's contents + * + * @param parent + * the parent composite + * @return Control + */ + @Override + protected Control createContents(Composite parent) { + Control contents = super.createContents(parent); + + // Set the title + setTitle(Labels.getString("LoaderDownloadDialog.title")); //$NON-NLS-1$ + + // Set the message + setMessage(Labels.getFormattedString("LoaderDownloadDialog.messageLineOne", + new String[] {controller.getLatestDownloadableDataLoaderVersion(), + AppUtil.DATALOADER_DOWNLOAD_URL})); //$NON-NLS-1$ + + // Set the image + setTitleImage(UIUtils.getImageRegistry().get("splashscreens")); //$NON-NLS-1$ + + return contents; + } + + /** + * Creates the gray area + * + * @param parent + * the parent composite + * @return Control + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite)super.createDialogArea(parent); + + return composite; + } + + /** + * Creates the buttons for the button bar + * + * @param parent + * the parent composite + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + // create all the buttons, in order + + createButton(parent, IDialogConstants.OK_ID, IDialogConstants.OK_LABEL, true); + } + + /** + * Sets the button behavior + */ + @Override + protected void buttonPressed(int buttonID) { + setReturnCode(buttonID); + close(); + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/LoaderWindow.java b/src/main/java/com/salesforce/dataloader/ui/LoaderWindow.java index cd37331ee..74480ca30 100644 --- a/src/main/java/com/salesforce/dataloader/ui/LoaderWindow.java +++ b/src/main/java/com/salesforce/dataloader/ui/LoaderWindow.java @@ -33,23 +33,21 @@ import org.eclipse.jface.action.Separator; import org.eclipse.jface.window.ApplicationWindow; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CLabel; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.*; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.RowLayout; import org.eclipse.swt.widgets.*; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.Config.ConfigListener; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.AppConfig.ConfigListener; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.ui.uiActions.*; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; +import com.salesforce.dataloader.util.AppUtil; /** * The main class for the Loader UI. @@ -81,6 +79,7 @@ private TreeMap createActionMap(Controller controlle private static LoaderWindow app; + private Shell shell; public LoaderWindow(Controller controller) { super(null); @@ -99,29 +98,22 @@ public LoaderWindow(Controller controller) { app = this; - addMenuBar(); addStatusLine(); - - // load values from last run - controller.getConfig().initLastRunFile(); this.controller = controller; - - final ConfigListener listener = new ConfigListener() { - @Override - public void configValueChanged(String key, String oldValue, String newValue) { - if (Config.BULK_API_ENABLED.equals(key)) { - boolean boolVal = false; - if (newValue != null) boolVal = Boolean.valueOf(newValue); - LoaderWindow.this.operationButtonsByIndex.get(OperationInfo.hard_delete).setEnabled(boolVal); - LoaderWindow.this.operationActionsByIndex.get(OperationInfo.hard_delete.getDialogIdx()).setEnabled( - boolVal); - getShell().redraw(); - } - } - }; - - this.controller.getConfig().addListener(listener); - + } + + public void refresh() { + AppConfig appConfig = controller.getAppConfig(); + boolean isBulkApiFlavorEnabled = appConfig.isBulkAPIEnabled() || appConfig.isBulkV2APIEnabled(); + LoaderWindow.this.operationButtonsByIndex.get(OperationInfo.hard_delete).setEnabled(isBulkApiFlavorEnabled); + LoaderWindow.this.operationActionsByIndex.get(OperationInfo.hard_delete.getDialogIdx()).setEnabled( + isBulkApiFlavorEnabled); + + // disable Undelete button and action if bulk API is enabled + LoaderWindow.this.operationButtonsByIndex.get(OperationInfo.undelete).setEnabled(!isBulkApiFlavorEnabled); + LoaderWindow.this.operationActionsByIndex.get(OperationInfo.undelete.getDialogIdx()).setEnabled( + !isBulkApiFlavorEnabled); + getShell().redraw(); } public void dispose() { @@ -144,24 +136,49 @@ public Controller getController() { * This runs the Loader Window */ public void run() { - + AppConfig appConfig = controller.getAppConfig(); + if (!appConfig.getBoolean(AppConfig.PROP_HIDE_WELCOME_SCREEN)) { + displayTitleDialog(Display.getDefault(), this.operationActionsByIndex, this.controller.getAppConfig()); + } + if (appConfig.getBoolean(AppConfig.PROP_SHOW_LOADER_UPGRADE_SCREEN)) { + displayUpgradeDialog(Display.getDefault()); + } setBlockOnOpen(true); + addMenuBar(); open(); - Display.getCurrent().dispose(); + Display currentDisplay = Display.getCurrent(); + if (currentDisplay != null) { + currentDisplay.dispose(); + } + } + + public void updateTitle(String suffix) { + if (suffix == null || suffix.isEmpty()) { + suffix = ""; + this.uiActionLogout.setEnabled(false); + } else { + suffix = " - " + suffix; + this.uiActionLogout.setEnabled(true); + } + this.shell.setText(Labels.getString("LoaderWindow.title") + suffix); } @Override protected void configureShell(Shell shell) { super.configureShell(shell); + this.shell = shell; // Set the title bar text - shell.setText(Labels.getString("LoaderWindow.title")); + updateTitle(null); shell.setSize(600, 400); + Point shellLocation = shell.getLocation(); + if (shellLocation.x < AppConfig.DEFAULT_WIZARD_X_OFFSET) { + shellLocation.x = AppConfig.DEFAULT_WIZARD_X_OFFSET; + shell.setLocation(shellLocation); + } shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); - - } private OperationUIAction getOperationAction(int i) { @@ -174,40 +191,44 @@ protected Control createContents(Composite parent) { createButtons(comp); - getStatusLineManager().setMessage(Labels.getString("LoaderWindow.chooseAction")); - - Config config = controller.getConfig(); - - if (!config.getBoolean(config.HIDE_WELCOME_SCREEN)) { - displayTitleDialog(Display.getDefault(), this.operationActionsByIndex, this.controller.getConfig()); - } - + getStatusLineManager().setMessage(Labels.getString("LoaderWindow.chooseAction")); comp.pack(); parent.pack(); - return parent; - } private Composite createContainer(Composite parent) { Composite comp = new Composite(parent, SWT.BORDER); - setBackground(comp); comp.setLayout(new FillLayout(SWT.VERTICAL)); - Label label = new Label(comp, SWT.CENTER); - setBackground(label); + + Composite logoComp = new Composite(comp, SWT.NONE); + logoComp.setLayout(new FillLayout(SWT.HORIZONTAL)); + CLabel label = new CLabel(logoComp, SWT.RIGHT); label.setImage(UIUtils.getImageRegistry().get("title_logo")); - comp.pack(); + GridData gridData = new GridData(); + gridData.horizontalAlignment = GridData.BEGINNING; + gridData.grabExcessHorizontalSpace = false; + gridData.verticalAlignment = GridData.VERTICAL_ALIGN_CENTER; + gridData.grabExcessVerticalSpace = true; + label.setLayoutData(gridData); + + label = new CLabel(logoComp, SWT.NONE); + FontData fd = new FontData("Verdana", 28, SWT.NORMAL); + label.setFont(new Font(Display.getCurrent(), fd)); + label.setText("data loader"); + gridData = new GridData(); + gridData.horizontalAlignment = GridData.BEGINNING; + gridData.grabExcessHorizontalSpace = false; + gridData.verticalAlignment = GridData.VERTICAL_ALIGN_FILL; + gridData.grabExcessVerticalSpace = true; + label.setLayoutData(gridData); + comp.pack(); return comp; } - private void setBackground(Control comp) { - comp.setBackground(new Color(Display.getCurrent(), 238,241,246)); - } - private void createButtons(Composite parent) { Composite buttons = new Composite(parent, SWT.NONE); - setBackground(buttons); RowLayout rowLayout = new RowLayout(SWT.HORIZONTAL); rowLayout.wrap = true; rowLayout.pack = false; @@ -222,16 +243,14 @@ private void createButtons(Composite parent) { for (final OperationInfo info : OperationInfo.ALL_OPERATIONS_IN_ORDER) { createOperationButton(buttons, info); } - // buttons.pack(); } private void createOperationButton(Composite parent, final OperationInfo info) { final Button butt = new Button(parent, SWT.PUSH | SWT.FLAT); butt.setText(info.getLabel()); - butt.setEnabled(info.isOperationAllowed(this.controller.getConfig())); - butt.setImage(info.getIconImage()); - butt.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + butt.setEnabled(info.isOperationAllowed(this.controller.getAppConfig())); + butt.setImage(info.getUIHelper().getIconImage()); butt.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent selectionEvent) { @@ -242,16 +261,30 @@ public void widgetSelected(SelectionEvent selectionEvent) { } private void displayTitleDialog(final Display display, final TreeMap map, - final Config cfg) { + final AppConfig cfg) { display.asyncExec(new Thread() { @Override public void run() { - LoaderTitleDialog dlg = new LoaderTitleDialog(display.getActiveShell(), cfg); + WelcomeScreenDialog dlg = new WelcomeScreenDialog(display.getActiveShell(), cfg); int result = dlg.open(); - for (Entry ent : map.entrySet()) + for (Entry ent : map.entrySet()) { if (result == ent.getKey()) ent.getValue().run(); + } + } + }); + } + + private void displayUpgradeDialog(final Display display) { + if (AppUtil.DATALOADER_VERSION.equals(controller.getLatestDownloadableDataLoaderVersion())) { + return; // running app's version matches with downloadable version. + } + display.asyncExec(new Thread() { + @Override + public void run() { + LoaderUpgradeDialog dlg = new LoaderUpgradeDialog(display.getActiveShell(), controller); + dlg.open(); } }); } diff --git a/src/main/java/com/salesforce/dataloader/ui/LoaderWizardDialog.java b/src/main/java/com/salesforce/dataloader/ui/LoaderWizardDialog.java index eec202f67..6fdedc895 100644 --- a/src/main/java/com/salesforce/dataloader/ui/LoaderWizardDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/LoaderWizardDialog.java @@ -43,6 +43,8 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; +import com.salesforce.dataloader.config.AppConfig; + /** * A dialog to show a wizard to the end user. *

@@ -92,9 +94,7 @@ public class LoaderWizardDialog extends LoaderTitleAreaDialog implements IWizard private SelectionAdapter cancelListener; private boolean isMovingToPreviousPage = false; private Composite pageContainer; - private PageContainerFillLayout pageContainerLayout = new PageContainerFillLayout(5, 5, 300, 225); - private int pageWidth = SWT.DEFAULT; - private int pageHeight = SWT.DEFAULT; + private PageContainerFillLayout pageContainerLayout = null; private static final String FOCUS_CONTROL = "focusControl"; //$NON-NLS-1$ private boolean lockedUI = false; private HashMap buttons; @@ -145,27 +145,30 @@ public PageContainerFillLayout(int mw, int mh, int minW, int minH) { */ @Override public Point computeSize(Composite composite, int wHint, int hHint, boolean force) { - if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT) return new Point(wHint, hHint); - Point result = null; + Point computedSizePoint = new Point(wHint, hHint); + if (wHint != SWT.DEFAULT && hHint != SWT.DEFAULT) { + computedSizePoint = new Point(wHint, hHint); + return computedSizePoint; + } Control[] children = composite.getChildren(); if (children.length > 0) { - result = new Point(0, 0); + computedSizePoint = new Point(0, 0); for (int i = 0; i < children.length; i++) { Point cp = children[i].computeSize(wHint, hHint, force); - result.x = Math.max(result.x, cp.x); - result.y = Math.max(result.y, cp.y); + computedSizePoint.x = Math.max(computedSizePoint.x, cp.x); + computedSizePoint.y = Math.max(computedSizePoint.y, cp.y); } - result.x = result.x + 2 * marginWidth; - result.y = result.y + 2 * marginHeight; + computedSizePoint.x = computedSizePoint.x + 2 * marginWidth; + computedSizePoint.y = computedSizePoint.y + 2 * marginHeight; } else { Rectangle rect = composite.getClientArea(); - result = new Point(rect.width, rect.height); + computedSizePoint = new Point(rect.width, rect.height); } - result.x = Math.max(result.x, minimumWidth); - result.y = Math.max(result.y, minimumHeight); - if (wHint != SWT.DEFAULT) result.x = wHint; - if (hHint != SWT.DEFAULT) result.y = hHint; - return result; + computedSizePoint.x = Math.max(computedSizePoint.x, minimumWidth); + computedSizePoint.y = Math.max(computedSizePoint.y, minimumHeight); + if (wHint != SWT.DEFAULT) computedSizePoint.x = wHint; + if (hHint != SWT.DEFAULT) computedSizePoint.y = hHint; + return computedSizePoint; } /** @@ -225,11 +228,16 @@ public void setPageLocation(Control w) { * @param newWizard * the wizard this dialog is working on */ - public LoaderWizardDialog(Shell parentShell, IWizard newWizard) { + public LoaderWizardDialog(Shell parentShell, IWizard newWizard, AppConfig appConfig) { super(parentShell); + Rectangle shellBounds = UIUtils.getPersistedWizardBounds(appConfig); + this.pageContainerLayout = new PageContainerFillLayout(5, 5, + shellBounds.width, + shellBounds.height); buttons = new HashMap<>(); setShellStyle(SWT.CLOSE | SWT.TITLE | SWT.BORDER | SWT.APPLICATION_MODAL | SWT.RESIZE); setWizard(newWizard); + setShellStyle(getShellStyle() | SWT.RESIZE); // since VAJava can't initialize an instance var with an anonymous // class outside a constructor we do it here: cancelListener = new SelectionAdapter() { @@ -286,6 +294,7 @@ protected void backPressed() { return; // set flag to indicate that we are moving back isMovingToPreviousPage = true; + ((WizardPage)currentPage).setPageComplete(false); // show the page showPage(page); } @@ -316,25 +325,6 @@ protected void buttonPressed(int buttonId) { } } - /** - * Calculates the difference in size between the given page and the page container. A larger page results in a - * positive delta. - * - * @param page - * the page - * @return the size difference encoded as a new Point(deltaWidth,deltaHeight) - */ - private Point calculatePageSizeDelta(IWizardPage page) { - Control pageControl = page.getControl(); - if (pageControl == null) - // control not created yet - return new Point(0, 0); - Point contentSize = pageControl.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); - Rectangle rect = pageContainerLayout.getClientArea(pageContainer); - Point containerSize = new Point(rect.width, rect.height); - return new Point(Math.max(0, contentSize.x - containerSize.x), Math.max(0, contentSize.y - containerSize.y)); - } - /* * (non-Javadoc) Method declared on Dialog. */ @@ -430,7 +420,7 @@ private Button createCancelButton(Composite parent) { button.setText(IDialogConstants.CANCEL_LABEL); setButtonLayoutData(button); button.setFont(parent.getFont()); - button.setData(new Integer(IDialogConstants.CANCEL_ID)); + button.setData(Integer.valueOf(IDialogConstants.CANCEL_ID)); button.addSelectionListener(cancelListener); return button; } @@ -445,7 +435,7 @@ private Button createCancelButton(Composite parent) { @Override protected Button getButton(int id) { if (id == IDialogConstants.CANCEL_ID) return cancelButton; - return buttons.get(new Integer(id)); + return buttons.get(Integer.valueOf(id)); } /** @@ -478,8 +468,6 @@ protected Control createDialogArea(Composite parent) { // Build the Page container pageContainer = createPageContainer(composite); GridData gd = new GridData(GridData.FILL_BOTH); - gd.widthHint = pageWidth; - gd.heightHint = pageHeight; pageContainer.setLayoutData(gd); pageContainer.setFont(parent.getFont()); // Insert a progress monitor @@ -728,6 +716,7 @@ protected void nextPressed() { // something must have happend getting the next page return; } + // show the next page showPage(page); } @@ -763,9 +752,9 @@ private boolean okToClose() { * the key * @see #saveEnableStateAndSet */ - private void restoreEnableState(Control w, Map h, String key) { + private void restoreEnableState(Control w, Map h, String key) { if (w != null) { - Boolean b = (Boolean)h.get(key); + Boolean b = h.get(key); if (b != null) w.setEnabled(b.booleanValue()); } } @@ -778,7 +767,7 @@ private void restoreEnableState(Control w, Map h, String key) { * a map containing the saved state as returned by saveUIState * @see #saveUIState */ - private void restoreUIState(Map state) { + private void restoreUIState(Map state) { restoreEnableState(backButton, state, "back"); //$NON-NLS-1$ restoreEnableState(nextButton, state, "next"); //$NON-NLS-1$ restoreEnableState(finishButton, state, "finish"); //$NON-NLS-1$ @@ -827,7 +816,7 @@ public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable */ private void saveEnableStateAndSet(Control w, Map h, String key, boolean enabled) { if (w != null) { - h.put(key, new Boolean(w.getEnabled())); + h.put(key, Boolean.valueOf(w.getEnabled())); w.setEnabled(enabled); } } @@ -902,8 +891,7 @@ public void setMinimumPageSize(Point size) { * @see #setPageSize(Point) */ public void setPageSize(int width, int height) { - pageWidth = width; - pageHeight = height; + // no op } /** @@ -936,7 +924,6 @@ protected void setWizard(IWizard newWizard) { // This allows the wizard to open to the correct size createPageControls(); // Ensure the dialog is large enough for the wizard - updateSizeForWizard(wizard); pageContainer.layout(true); } } else { @@ -1000,6 +987,8 @@ private void updateForPage(IWizardPage page) { IWizardPage oldPage = currentPage; currentPage = page; currentPage.setVisible(true); + OperationPage opPage = (OperationPage)currentPage; + opPage.setPageComplete(); if (oldPage != null) oldPage.setVisible(false); // update the dialog controls update(); @@ -1037,14 +1026,15 @@ private void showStartingPage() { * the saved UI state as returned by aboutToStart * @see #aboutToStart */ + @SuppressWarnings("unchecked") private void stopped(Object savedState) { if (getShell() != null) { if (wizard.needsProgressMonitor()) { progressMonitorPart.setVisible(false); progressMonitorPart.removeFromCancelComponent(cancelButton); } - Map state = (Map)savedState; - restoreUIState(state); + Map state = (Map)savedState; + restoreUIState((Map)state); cancelButton.addSelectionListener(cancelListener); setDisplayCursor(null); cancelButton.setCursor(null); @@ -1067,6 +1057,8 @@ protected void update() { updateTitleBar(); // Update the buttons updateButtons(); + // update size + updateSize(); } /* @@ -1076,8 +1068,8 @@ protected void update() { public void updateButtons() { boolean canFlipToNextPage = false; boolean canFinish = wizard.canFinish(); - if (backButton != null) backButton.setEnabled(currentPage.getPreviousPage() != null); - if (nextButton != null) { + if (backButton != null) backButton.setEnabled(currentPage != null && currentPage.getPreviousPage() != null); + if (nextButton != null && currentPage != null) { canFlipToNextPage = currentPage.canFlipToNextPage(); nextButton.setEnabled(canFlipToNextPage); } @@ -1130,21 +1122,6 @@ public void updateMessage() { setErrorMessage(currentPage.getErrorMessage()); } - /** - * Changes the shell size to the given size, ensuring that it is no larger than the display bounds. - * - * @param width - * the shell width - * @param height - * the shell height - */ - private void setShellSize(int width, int height) { - Rectangle size = getShell().getBounds(); - size.height = height; - size.width = width; - getShell().setBounds(getConstrainedShellBounds(size)); - } - /** * Computes the correct dialog size for the current page and resizes its shell if nessessary. Also causes the * container to refresh its layout. @@ -1155,7 +1132,9 @@ private void setShellSize(int width, int height) { */ protected void updateSize(IWizardPage page) { if (page == null || page.getControl() == null) return; - updateSizeForPage(page); + Shell shell = this.getShell(); + Rectangle savedBounds = UIUtils.getPersistedWizardBounds(((OperationPage)page).controller.getAppConfig()); + shell.setBounds(getConstrainedShellBounds(savedBounds)); pageContainerLayout.layoutPage(page.getControl()); } @@ -1169,47 +1148,6 @@ public void updateSize() { updateSize(currentPage); } - /** - * Computes the correct dialog size for the given page and resizes its shell if nessessary. - * - * @param page - * the wizard page - */ - private void updateSizeForPage(IWizardPage page) { - // ensure the page container is large enough - Point delta = calculatePageSizeDelta(page); - if (delta.x > 0 || delta.y > 0) { - // increase the size of the shell - Shell shell = getShell(); - Point shellSize = shell.getSize(); - setShellSize(shellSize.x + delta.x, shellSize.y + delta.y); - constrainShellSize(); - } - } - - /** - * Computes the correct dialog size for the given wizard and resizes its shell if nessessary. - * - * @param sizingWizard - * the wizard - */ - private void updateSizeForWizard(IWizard sizingWizard) { - Point delta = new Point(0, 0); - IWizardPage[] pages = sizingWizard.getPages(); - for (int i = 0; i < pages.length; i++) { - // ensure the page container is large enough - Point pageDelta = calculatePageSizeDelta(pages[i]); - delta.x = Math.max(delta.x, pageDelta.x); - delta.y = Math.max(delta.y, pageDelta.y); - } - if (delta.x > 0 || delta.y > 0) { - // increase the size of the shell - Shell shell = getShell(); - Point shellSize = shell.getSize(); - setShellSize(shellSize.x + delta.x, shellSize.y + delta.y); - } - } - /* * (non-Javadoc) Method declared on IWizardContainer. */ @@ -1246,7 +1184,7 @@ protected Button createButton(Composite parent, int id, String label,boolean def Button button = new Button(parent, SWT.PUSH | SWT.FLAT); button.setText(label); button.setFont(JFaceResources.getDialogFont()); - button.setData(new Integer(id)); + button.setData(Integer.valueOf(id)); button.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { buttonPressed(((Integer) event.widget.getData()).intValue()); @@ -1259,10 +1197,9 @@ public void widgetSelected(SelectionEvent event) { } } - buttons.put(new Integer(id), button); + buttons.put(Integer.valueOf(id), button); setButtonLayoutData(button); return button; } - } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/LoginAdvancedControl.java b/src/main/java/com/salesforce/dataloader/ui/LoginAdvancedControl.java deleted file mode 100644 index 5db7a6a86..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/LoginAdvancedControl.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.model.LoginCriteria; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.*; - -/** - * LoginAdvancedControl is used to connect to SF with a sessionid - */ -public class LoginAdvancedControl extends Composite { - private final Text loginUrl; - private final Button loginButton; - private final Text sessionId; - private final AuthenticationRunner authenticator; - private final Label loginLabel; - private final Text userName; - - public LoginAdvancedControl(Composite parent, int style, AuthenticationRunner authenticator) { - super(parent, style); - this.authenticator = authenticator; - - Grid12 grid = new Grid12(this, 40, 20); - - grid.createLabel(4, Labels.getString("SettingsPage.username")); - userName = grid.createText(6, SWT.BORDER | SWT.FILL, authenticator.getConfig().getString(Config.USERNAME)); - grid.createPadding(2); - - grid.createLabel(4, Labels.getString("SettingsPage.sessionId")); - sessionId = grid.createText(6, SWT.BORDER, authenticator.getConfig().getString(Config.SFDC_INTERNAL_SESSION_ID)); - grid.createPadding(2); - - grid.createLabel(4, Labels.getString("SettingsPage.instServerUrl")); - loginUrl = grid.createText(6, SWT.BORDER, authenticator.getConfig().getString(Config.ENDPOINT)); - grid.createPadding(2); - - loginLabel = grid.createLabel(8, ""); - loginButton = grid.createButton(2, SWT.PUSH | SWT.FILL, Labels.getString("SettingsPage.login")); - loginButton.addListener(SWT.Selection, this::loginButton_Clicked); - grid.createPadding(2); - } - - private void loginButton_Clicked(Event event) { - LoginCriteria criteria = new LoginCriteria(LoginCriteria.Advanced); - criteria.setInstanceUrl(loginUrl.getText()); - criteria.setSessionId(sessionId.getText()); - criteria.setUserName(userName.getText()); - authenticator.login(criteria, loginLabel::setText); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/LoginDefaultControl.java b/src/main/java/com/salesforce/dataloader/ui/LoginDefaultControl.java deleted file mode 100644 index fbc130f42..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/LoginDefaultControl.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.ui; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.model.LoginCriteria; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.CCombo; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Label; - -import java.util.ArrayList; - -/** - * Login default control is the oauth login - */ -public class LoginDefaultControl extends Composite { - private final Button loginButton; - private final CCombo environment; - private final AuthenticationRunner authenticator; - private final Label loginLabel; - - public LoginDefaultControl(Composite parent, int style, AuthenticationRunner authenticator) { - super(parent, style); - this.authenticator = authenticator; - - Grid12 grid = new Grid12(this, 40, 20); - - grid.createLabel(4, Labels.getString("SettingsPage.environment")); - ArrayList environments = authenticator.getConfig().getStrings(Config.OAUTH_ENVIRONMENTS); - environment = grid.createCombo(6, SWT.DROP_DOWN | SWT.BORDER, environments); - String currentEnvironment = authenticator.getConfig().getString(Config.OAUTH_ENVIRONMENT); - if (environments.contains(currentEnvironment)) { - environment.setText(currentEnvironment); - } - - grid.createPadding(2); - - loginLabel = grid.createLabel(8, ""); - loginButton = grid.createButton(2, SWT.PUSH | SWT.FILL | SWT.FLAT, Labels.getString("SettingsPage.login")); - loginButton.addListener(SWT.Selection, this::loginButton_Clicked); - grid.createPadding(2); - } - - private void loginButton_Clicked(Event event) { - LoginCriteria criteria = new LoginCriteria(LoginCriteria.Default); - criteria.setEnvironment(environment.getText()); - authenticator.login(criteria, loginLabel::setText); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/LoginPage.java b/src/main/java/com/salesforce/dataloader/ui/LoginPage.java new file mode 100644 index 000000000..309684bb6 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/LoginPage.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; + +/** + * Describe your class here. + * + * @author Lexi Viripaeff + * @since 6.0 + */ +public class LoginPage extends OperationPage { + + private AuthenticationRunner authenticator; + private OAuthLoginControl oauthControl; + private UsernamePasswordLoginControl unamePwdLoginControl; + private UsernamePasswordLoginControl sessionIdLoginControl; + private Grid12 grid; + private Composite control; + private String nextPageName = DataSelectionPage.class.getSimpleName(); + + public LoginPage(Controller controller) { + super("LoginPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ + setPageComplete(); + } + + @Override + public void createControl(Composite parent) { + getShell().setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ + + AppConfig appConfig = controller.getAppConfig(); + control = new Composite(parent, SWT.FILL); + grid = new Grid12(control, 40, false, true); + authenticator = new AuthenticationRunner(getShell(), appConfig, controller); + + Button[] layouts = new Button[3]; + grid.createPadding(2); + layouts[0] = grid.createButton(2, SWT.RADIO, Labels.getString("LoginPage.loginDefault")); + layouts[1] = grid.createButton(4, SWT.RADIO, Labels.getString("LoginPage.loginStandard")); + layouts[2] = grid.createButton(2, SWT.RADIO, Labels.getString("LoginPage.loginAdvanced")); + grid.createPadding(2); + + oauthControl = new OAuthLoginControl(control, SWT.FILL, this, authenticator); + oauthControl.setLayoutData(grid.createCell(12)); + unamePwdLoginControl = new UsernamePasswordLoginControl(control, SWT.FILL, this, authenticator, false); + unamePwdLoginControl.setLayoutData(grid.createCell(10)); + sessionIdLoginControl = new UsernamePasswordLoginControl(control, SWT.FILL, this, authenticator, true); + sessionIdLoginControl.setLayoutData(grid.createCell(12)); + + setControl(control); + + layouts[0].addListener(SWT.Selection, this::selectOAuth); + layouts[1].addListener(SWT.Selection, this::selectUnamePwd); + layouts[2].addListener(SWT.Selection, this::selectSessionId); + + //turn off oauth options if no configured environments found + if (appConfig.getStrings(AppConfig.PROP_SERVER_ENVIRONMENTS).size() > 0) { + layouts[0].setSelection(true); + selectOAuth(null); + } else { + grid.hide(layouts[0]); + layouts[1].setSelection(true); + selectUnamePwd(null); + } + if (!appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL)){ + grid.hide(layouts[2]); + if (!layouts[0].getVisible()){ + //no options other than standard so don't present them + grid.hide(layouts[1]); + } + } + setupPage(); + } + + public void setNextPageName(String name) { + this.nextPageName = name; + } + + /** + * Loads DataSelectionPage. To be overridden by subclasses for special behavior. + * + * @param controller + */ + private void loadDataSelectionPage(Controller controller) { + ((OperationPage)getWizard().getPage(this.nextPageName)).setupPage(); //$NON-NLS-1$ + if (canFlipToNextPage()) { + IWizardPage page = getNextPage(); + if (page != null) { + getContainer().showPage(page); + } + // following is a hack to correctly resize the list showing + // sObjects in DataSelectionPage on Mac. + // --- Start --- + setIgnoreBoundsChanges(true); + Rectangle shellBounds = this.getShell().getBounds(); + shellBounds.height++; + this.getShell().setBounds(shellBounds); + shellBounds.height--; + this.getShell().setBounds(shellBounds); + setIgnoreBoundsChanges(false); + // --- End ---- + } + } + + /** + * Need to subclass this function to prevent the getNextPage() function being called before the button is clicked. + */ + @Override + public boolean canFlipToNextPage() { + return isPageComplete(); + } + + /** + * Returns the next page, login. + * + * @return IWizardPage + */ + @Override + public IWizardPage getNextPage() { + return super.getNextPage(); + } + + public static boolean isNeeded(Controller controller) { + return (!controller.isLoggedIn()); + } + + private void selectSessionId(Event event) { + show(sessionIdLoginControl); + } + + private void selectUnamePwd(Event event) { + show(unamePwdLoginControl); + } + + private void selectOAuth(Event event) { + show(oauthControl); + } + + private void show(Composite showControl) { + grid.hide(oauthControl); + grid.hide(unamePwdLoginControl); + grid.hide(sessionIdLoginControl); + grid.show(showControl); + + control.layout(false); + } + + @Override + public void setPageComplete() { + setPageComplete(controller.isLoggedIn()); + if (controller.isLoggedIn()){ + loadDataSelectionPage(controller); + } + } + + @Override + protected boolean setupPagePostLogin() { + return true; + } + + @Override + protected String getConfigInfo() { + return ""; + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/LoginStandardControl.java b/src/main/java/com/salesforce/dataloader/ui/LoginStandardControl.java deleted file mode 100644 index 5afb46e73..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/LoginStandardControl.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.model.LoginCriteria; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.*; - -/** - * LoginStandardControl is the way to login to the api - */ -public class LoginStandardControl extends Composite { - - private final Grid12 grid; - private final Button loginButton; - private final Text userName; - private final Text password; - private final Text instanceUrl; - private final AuthenticationRunner authentication; - private final Label loginLabel; - - public LoginStandardControl(Composite parent, int style, AuthenticationRunner authentication) { - super(parent, style); - this.authentication = authentication; - grid = new Grid12(this, 40, 20); - - grid.createLabel(4, Labels.getString("SettingsPage.username")); - userName = grid.createText(6, SWT.BORDER | SWT.FILL, authentication.getConfig().getString(Config.USERNAME)); - grid.createPadding(2); - - grid.createLabel(4, Labels.getString("SettingsPage.password")); - password = grid.createText(6, SWT.PASSWORD | SWT.BORDER, ""); - grid.createPadding(2); - - grid.createLabel(4, Labels.getString("SettingsPage.instServerUrl")); - instanceUrl = grid.createText(6, SWT.BORDER, authentication.getConfig().getString(Config.ENDPOINT)); - grid.createPadding(2); - - loginLabel = grid.createLabel(8, ""); - loginButton = grid.createButton(2, SWT.PUSH | SWT.FILL | SWT.FLAT, Labels.getString("SettingsPage.login")); - loginButton.addListener(SWT.Selection, this::loginButton_Clicked); - grid.createPadding(2); - } - - private void loginButton_Clicked(Event event) { - LoginCriteria criteria = new LoginCriteria(LoginCriteria.Standard); - criteria.setInstanceUrl(instanceUrl.getText()); - criteria.setUserName(userName.getText()); - criteria.setPassword(password.getText()); - authentication.login(criteria, loginLabel::setText); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/MappingDialog.java b/src/main/java/com/salesforce/dataloader/ui/MappingDialog.java index 9130285e2..ea99b27de 100644 --- a/src/main/java/com/salesforce/dataloader/ui/MappingDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/MappingDialog.java @@ -29,35 +29,52 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.security.GeneralSecurityException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; -import org.apache.log4j.Logger; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.*; -import org.eclipse.swt.events.*; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.*; - -import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; + +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.mapping.LoadMapper; import com.salesforce.dataloader.ui.mapping.*; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.soap.partner.Field; /** * This class creates the mapping dialog */ -public class MappingDialog extends Dialog { - private String input; - - private Controller controller; - private final Logger logger = Logger.getLogger(MappingDialog.class); - +public class MappingDialog extends WizardDialog { //the two tableViewers private TableViewer sforceTblViewer; private TableViewer mappingTblViewer; @@ -70,28 +87,27 @@ public class MappingDialog extends Dialog { public static final int MAPPING_DAO = 0; public static final int MAPPING_SFORCE = 1; - //the current list of fields - private Field[] fields; + //unmapped sobject fields of the selected sobject + private Field[] unmappedSobjectFields; - //all the fields - private Field[] allFields; + //all sobject fields of the selected sobject + private final Field[] allSobjectFields; //restore for the old values on cancel private Properties restore; private LoadMapper mapper; - private Field[] sforceFieldInfo; private MappingPage page; private HashSet mappedFields; - - public void setSforceFieldInfo(Field[] sforceFieldInfo) { - this.sforceFieldInfo = sforceFieldInfo; + private Shell parentShell; + private Shell dialogShell; + private Text sforceFieldsSearch; + private boolean dragNDropCancelled = false; + + public void setUnmappedSobjectFields(Field[] fields) { + this.unmappedSobjectFields = fields; } - - public void setFields(Field[] newFields) { - this.fields = newFields; - } - + public void setMapper(LoadMapper mapper) { this.mapper = mapper; this.restore = new Properties(); @@ -114,67 +130,14 @@ public void setMapper(LoadMapper mapper) { */ public MappingDialog(Shell parent, Controller controller, MappingPage page) { // Pass the default styles here - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL); - this.controller = controller; + super(parent, controller); this.page = page; - - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public MappingDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - - setText(Labels.getString("MappingDialog.title")); //$NON-NLS-1$ - } - - /** - * Gets the input - * - * @return String - */ - public String getInput() { - return input; + this.parentShell = parent; + this.allSobjectFields = page.getMappableSalesforceFields(); } - - /** - * Sets the input - * - * @param input - * the new input - */ - public void setInput(String input) { - this.input = input; - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public String open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle() | SWT.RESIZE); - shell.setText(getText()); - shell.setSize(600, 600); - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the entered value, or null - return input; + + public Shell getParent() { + return this.parentShell; } /** @@ -183,7 +146,8 @@ public String open() { * @param shell * the dialog window */ - private void createContents(final Shell shell) { + protected void createContents(final Shell shell) { + this.dialogShell = shell; shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ shell.setLayout(new GridLayout(1, false)); GridData data; @@ -191,7 +155,7 @@ private void createContents(final Shell shell) { //top label Label label = new Label(shell, SWT.NONE); label.setText(Labels.getString("MappingDialog.matchlabel")); //$NON-NLS-1$ - + //buttons Composite comp = new Composite(shell, SWT.NONE); comp.setLayout(new GridLayout(2, false)); @@ -216,11 +180,19 @@ public void widgetSelected(SelectionEvent event) { autoMatchFields(); } }); + + sforceFieldsSearch = new Text(shell, SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH); + sforceFieldsSearch.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + sforceFieldsSearch.addListener(SWT.KeyUp, new Listener() { + public void handleEvent(Event e) { + sforceTblViewer.refresh(); + } + }); /////////////////////////////////////////////// //InitializeSforceViewer /////////////////////////////////////////////// - initializeSforceViewer(shell); + initializeSforceViewer(shell, sforceFieldsSearch); Label sep1 = new Label(shell, SWT.HORIZONTAL | SWT.SEPARATOR); sep1.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); @@ -258,9 +230,8 @@ public void widgetSelected(SelectionEvent event) { @Override public void widgetSelected(SelectionEvent event) { //refresh the mapping page view - page.updateMapping(); - page.packMappingColumns(); - + page.updateMappingTblViewer(); + page.setPageComplete(); shell.close(); } }); @@ -274,9 +245,6 @@ public void widgetSelected(SelectionEvent event) { @Override public void widgetSelected(SelectionEvent event) { FileDialog dlg = new FileDialog(shell, SWT.SAVE); - Config config = controller.getConfig(); - dlg.setFileName(config.getString(Config.MAPPING_FILE)); - dlg.setFilterPath(config.getString(Config.MAPPING_FILE)); dlg.setFilterExtensions(new String[] { "*.sdl" }); String filename = dlg.open(); boolean cancel = false; @@ -299,17 +267,18 @@ public void widgetSelected(SelectionEvent event) { } if (!cancel) { - config.setValue(Config.MAPPING_FILE, filename); try { mapper.save(filename); } catch (IOException e1) { logger.error(Labels.getString("MappingDialog.errorSave"), e1); //$NON-NLS-1$ } + } //refresh the mapping page view - page.updateMapping(); - page.packMappingColumns(); + page.updateMappingTblViewer(); + page.setPageComplete(); + shell.close(); } }); @@ -325,7 +294,7 @@ public void widgetSelected(SelectionEvent event) { public void widgetSelected(SelectionEvent event) { //revert the Mapping back - mapper.clearMap(); + mapper.clearMappings(); mapper.putPropertyFileMappings(restore); shell.close(); @@ -337,6 +306,14 @@ public void widgetSelected(SelectionEvent event) { // to dismiss shell.setDefaultButton(ok); } + + public void setIsDragNDropCancelled(boolean cancelled) { + this.dragNDropCancelled = cancelled; + } + + public boolean isDragActionCancelled() { + return this.dragNDropCancelled; + } private void initializeMappingViewer(Shell shell) { GridData data; @@ -344,20 +321,24 @@ private void initializeMappingViewer(Shell shell) { mappingTblViewer = new TableViewer(shell, SWT.FULL_SELECTION); mappingTblViewer.setContentProvider(new MappingContentProvider()); mappingTblViewer.setLabelProvider(new MappingLabelProvider()); - mappingTblViewer.setSorter(new MappingViewerSorter()); + + data = new GridData(GridData.FILL_BOTH); //add drop support int ops = DND.DROP_MOVE; Transfer[] transfers = new Transfer[] { TextTransfer.getInstance() }; - mappingTblViewer.addDropSupport(ops, transfers, new MappingDropAdapter(mappingTblViewer, this)); + mappingTblViewer.addDropSupport(ops, transfers, new MappingDropAdapter(mappingTblViewer, this, getController())); //add drag support mappingTblViewer.addDragSupport(ops, transfers, new SforceDragListener(mappingTblViewer, this)); // Set up the sforce table Table mappingTable = mappingTblViewer.getTable(); + Rectangle shellBounds = getPersistedDialogBounds(); data = new GridData(GridData.FILL_BOTH); - data.heightHint = 200; + data.widthHint = shellBounds.width; + data.heightHint = shellBounds.height / 3; + // data.heightHint = shellBounds.height; mappingTable.setLayoutData(data); //add key listener to process deletes @@ -365,21 +346,18 @@ private void initializeMappingViewer(Shell shell) { @Override public void keyPressed(KeyEvent event) { //\u007f is delete - if (event.character == '\u007f') { + if (event.character == '\u007f' || event.character == '\b') { IStructuredSelection selection = (IStructuredSelection)mappingTblViewer.getSelection(); - for (Iterator it = selection.iterator(); it.hasNext();) { + for (Iterator it = selection.iterator(); it.hasNext();) { @SuppressWarnings("unchecked") Map.Entry elem = (Entry)it.next(); String oldSforce = elem.getValue(); if (oldSforce != null && oldSforce.length() > 0) { //clear the sforce - replenishField(oldSforce); - + replenishMappedSforceFields(oldSforce); elem.setValue(""); mapper.removeMapping(elem.getKey()); - packMappingColumns(); - mappingTblViewer.refresh(); } } } @@ -389,24 +367,32 @@ public void keyPressed(KeyEvent event) { public void keyReleased(KeyEvent event) {} }); - // Add the first column - name - TableColumn tc = new TableColumn(mappingTable, SWT.LEFT); - tc.setText(Labels.getString("MappingDialog.fileColumn")); //$NON-NLS-1$ - tc.addSelectionListener(new SelectionAdapter() { + // Add the first column - column header in CSV file + TableColumn csvFieldsCol = new TableColumn(mappingTable, SWT.LEFT); + String headerStr = Labels.getString("MappingDialog.fileColumn"); + csvFieldsCol.setText(headerStr); //$NON-NLS-1$ + csvFieldsCol.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - ((MappingViewerSorter)mappingTblViewer.getSorter()).doSort(MAPPING_DAO); + if (mappingTblViewer.getComparator() == null) { + mappingTblViewer.setComparator(new MappingViewerComparator()); + } + ((MappingViewerComparator)mappingTblViewer.getComparator()).doSort(MAPPING_DAO); mappingTblViewer.refresh(); } }); - //Add the second column - label - tc = new TableColumn(mappingTable, SWT.LEFT); - tc.setText(Labels.getString("MappingDialog.fileName")); //$NON-NLS-1$ - tc.addSelectionListener(new SelectionAdapter() { + //Add the second column - name of Salesforce object field + TableColumn sforceFieldNamesCol = new TableColumn(mappingTable, SWT.LEFT); + headerStr = Labels.getString("MappingDialog.sforceFieldName"); + sforceFieldNamesCol.setText(headerStr); //$NON-NLS-1$ + sforceFieldNamesCol.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - ((MappingViewerSorter)mappingTblViewer.getSorter()).doSort(MAPPING_SFORCE); + if (mappingTblViewer.getComparator() == null) { + mappingTblViewer.setComparator(new MappingViewerComparator()); + } + ((MappingViewerComparator)mappingTblViewer.getComparator()).doSort(MAPPING_SFORCE); mappingTblViewer.refresh(); } }); @@ -424,17 +410,17 @@ public void widgetSelected(SelectionEvent event) { if (mappingTable.getItemCount() > 0) { mappingTable.showItem(mappingTable.getItem(0)); } - } - private void initializeSforceViewer(Shell shell) { + private void initializeSforceViewer(Shell shell, Text sforceFieldsSearch) { GridData data; //sforce field table viewer sforceTblViewer = new TableViewer(shell, SWT.FULL_SELECTION); sforceTblViewer.setContentProvider(new SforceContentProvider()); - sforceTblViewer.setLabelProvider(new SforceLabelProvider()); - sforceTblViewer.setSorter(new SforceViewerSorter()); + sforceTblViewer.setLabelProvider(new SforceLabelProvider(this.getController())); + sforceTblViewer.setComparator(new SforceViewerComparator()); + sforceTblViewer.addFilter(new SforceFieldsFilter(sforceFieldsSearch)); //add drag support int ops = DND.DROP_MOVE; @@ -447,38 +433,40 @@ private void initializeSforceViewer(Shell shell) { // Set up the sforce table Table sforceTable = sforceTblViewer.getTable(); data = new GridData(GridData.FILL_BOTH); - data.heightHint = 150; + Rectangle shellBounds = getPersistedDialogBounds(); + data.widthHint = shellBounds.width; + data.heightHint = shellBounds.height / 3; sforceTable.setLayoutData(data); // Add the first column - name TableColumn tc = new TableColumn(sforceTable, SWT.LEFT); - tc.setText(Labels.getString("MappingDialog.sforceName")); //$NON-NLS-1$ + tc.setText(Labels.getString("MappingDialog.sforceFieldName")); //$NON-NLS-1$ tc.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - ((SforceViewerSorter)sforceTblViewer.getSorter()).doSort(FIELD_NAME); + ((SforceViewerComparator)sforceTblViewer.getComparator()).doSort(FIELD_NAME); sforceTblViewer.refresh(); } }); //Add the second column - label tc = new TableColumn(sforceTable, SWT.LEFT); - tc.setText(Labels.getString("MappingDialog.sforceLabel")); //$NON-NLS-1$ + tc.setText(Labels.getString("MappingDialog.sforceFieldLabel")); //$NON-NLS-1$ tc.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - ((SforceViewerSorter)sforceTblViewer.getSorter()).doSort(FIELD_LABEL); + ((SforceViewerComparator)sforceTblViewer.getComparator()).doSort(FIELD_LABEL); sforceTblViewer.refresh(); } }); // Add the third column - type - tc = new TableColumn(sforceTable, SWT.RIGHT); - tc.setText(Labels.getString("MappingDialog.sforceType")); //$NON-NLS-1$ + tc = new TableColumn(sforceTable, SWT.LEFT); + tc.setText(Labels.getString("MappingDialog.sforceFieldType")); //$NON-NLS-1$ tc.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { - ((SforceViewerSorter)sforceTblViewer.getSorter()).doSort(FIELD_TYPE); + ((SforceViewerComparator)sforceTblViewer.getComparator()).doSort(FIELD_TYPE); sforceTblViewer.refresh(); } }); @@ -499,51 +487,23 @@ public void widgetSelected(SelectionEvent event) { } } + + protected void setShellBounds(Shell dialogShell) { + Rectangle shellBounds = getPersistedDialogBounds(); + dialogShell.setBounds(shellBounds); + dialogShell.addListener(SWT.Resize, this::persistedDialogShellBoundsChanged); + packMappingColumns(); + packSforceColumns(); + } private void autoMatchFields() { - - LinkedList fieldList = new LinkedList(Arrays.asList(fields)); - //first match on name, then label - ListIterator iterator = fieldList.listIterator(); - Field field; - while (iterator.hasNext()) { - field = iterator.next(); - String fieldName = field.getName(); - String fieldLabel = field.getLabel(); - String mappingSource = null; - - // field is already mapped - // TODO: check with lexi if this is intended use of automatch - if(mappedFields.contains(fieldName)) { - continue; - } - - if (mapper.hasDaoColumn(fieldName)) { - mappingSource = fieldName; - } else if (mapper.hasDaoColumn(fieldLabel)) { - mappingSource = fieldLabel; - } - - if(mappingSource != null) { - // don't overwrite the fields that already have been mapped - String oldFieldName = mapper.getMapping(mappingSource); - if(oldFieldName == null || oldFieldName.length() == 0) { - mapper.putMapping(mappingSource, fieldName); - } - iterator.remove(); - } - } - - fields = fieldList.toArray(new Field[fieldList.size()]); - - sforceTblViewer.setInput(fields); - sforceTblViewer.refresh(); - mappingTblViewer.refresh(); - + this.unmappedSobjectFields = this.page.autoMatchFieldsAndGetUnmappedFields(); + sforceTblViewer.setInput(this.unmappedSobjectFields); + updateMapping(); //pack the columns packMappingColumns(); packSforceColumns(); - + } public void packMappingColumns() { @@ -551,8 +511,11 @@ public void packMappingColumns() { // Pack the columns for (int i = 0, n = mappingTable.getColumnCount(); i < n; i++) { mappingTable.getColumn(i).pack(); - } + mappingTblViewer.setInput(this.mapper); + mappingTblViewer.refresh(); + mappingTable.redraw(); + UIUtils.setTableColWidth(mappingTable); } private void packSforceColumns() { @@ -561,29 +524,30 @@ private void packSforceColumns() { for (int i = 0, n = sforceTable.getColumnCount(); i < n; i++) { sforceTable.getColumn(i).pack(); } - } - - public void replenishField(String fieldName) { - //find the Field object to add to the current list. - Field field; - for (int i = 0; i < allFields.length; i++) { - field = allFields[i]; - if (field.getName().equals(fieldName)) { - ArrayList fieldArray = new ArrayList(Arrays.asList(fields)); - - //else add the field - fieldArray.add(field); - fields = fieldArray.toArray(new Field[fieldArray.size()]); - - //then refresh - sforceTblViewer.setInput(fields); - sforceTblViewer.refresh(); + sforceTblViewer.refresh(); + sforceTable.redraw(); + UIUtils.setTableColWidth(sforceTable); - packSforceColumns(); + } - return; + public void replenishMappedSforceFields(String fieldNameList) { + String[] fieldNameListArray = fieldNameList.split(AppUtil.COMMA); + ArrayList fieldList = new ArrayList(Arrays.asList(this.unmappedSobjectFields)); + for (String fieldNameToReplenish : fieldNameListArray) { + fieldNameToReplenish = fieldNameToReplenish.strip(); + //find the Field object to add to the current list. + Field field; + for (int i = 0; i < allSobjectFields.length; i++) { + field = allSobjectFields[i]; + if (field.getName().equals(fieldNameToReplenish)) { + fieldList.add(field); + } } } + this.unmappedSobjectFields = fieldList.toArray(new Field[fieldList.size()]); + sforceTblViewer.setInput(this.unmappedSobjectFields); + packSforceColumns(); + return; } /** @@ -591,75 +555,42 @@ public void replenishField(String fieldName) { */ private void updateSforce() { - ArrayList mappableFieldList = new ArrayList(); - ArrayList allFieldList = new ArrayList(); - Field field; - OperationInfo operation = controller.getConfig().getOperationInfo(); - for (int i = 0; i < sforceFieldInfo.length; i++) { - - field = sforceFieldInfo[i]; - boolean isMappable = false; - switch (operation) { - case insert: - if (field.isCreateable()) { - isMappable = true; - } - break; - case delete: - case hard_delete: - if (field.getType().toString().toLowerCase().equals("id")) { - isMappable = true; - } - break; - case upsert: - if (field.isUpdateable() || field.isCreateable() - || field.getType().toString().toLowerCase().equals("id")) { - isMappable = true; - } - break; - case update: - if (field.isUpdateable() || field.getType().toString().toLowerCase().equals("id")) { - isMappable = true; - } - break; - default: - throw new UnsupportedOperationException(); - } - // only add the field to mappings if it's not already used in mapping - if(isMappable) { - if(!mappedFields.contains(field.getName())) { - mappableFieldList.add(field); - } - // this list is for all fields in case map is reset - allFieldList.add(field); + ArrayList unmappedSobjectFieldList = new ArrayList(); + AppConfig appConfig = getController().getAppConfig(); + String extIdField = appConfig.getString(AppConfig.PROP_IDLOOKUP_FIELD); + if(extIdField == null) { + extIdField = ""; + } else { + extIdField = extIdField.toLowerCase(); + } + for (Field sobjectField : allSobjectFields) { + if(!mappedFields.contains(sobjectField.getName())) { + unmappedSobjectFieldList.add(sobjectField); } } - - fields = mappableFieldList.toArray(new Field[mappableFieldList.size()]); - allFields = allFieldList.toArray(new Field[allFieldList.size()]); + this.unmappedSobjectFields = unmappedSobjectFieldList.toArray(new Field[unmappedSobjectFieldList.size()]); // Set the table viewer's input - sforceTblViewer.setInput(fields); + sforceTblViewer.setInput(this.unmappedSobjectFields); } /** * Clears the mapping */ private void clearMapping() { - mapper.clearMap(); - // restore the fields that were mapped before + mapper.clearMappings(); + // restore the fields in sforceTblViewer that were mapped before for(String fieldName : mappedFields) { - replenishField(fieldName); + replenishMappedSforceFields(fieldName); } mappedFields.clear(); - mappingTblViewer.refresh(); + packMappingColumns(); } /** * Responsible for updating the mapping model */ private void updateMapping() { - // Set the table viewer's input mappingTblViewer.setInput(mapper); } @@ -667,5 +598,66 @@ private void updateMapping() { public LoadMapper getMapper() { return this.mapper; } + + private void persistedDialogShellBoundsChanged(Event event) { + switch (event.type) { + case SWT.Resize: + case SWT.Move: + if (!this.dialogShell.isVisible()) { + return; + } + AppConfig appConfig = this.getController().getAppConfig(); + Rectangle shellBounds = this.dialogShell.getBounds(); + appConfig.setValue(AppConfig.DIALOG_BOUNDS_PREFIX + this.getClass().getSimpleName() + AppConfig.DIALOG_WIDTH_SUFFIX, shellBounds.width); + appConfig.setValue(AppConfig.DIALOG_BOUNDS_PREFIX + this.getClass().getSimpleName() + AppConfig.DIALOG_HEIGHT_SUFFIX, shellBounds.height); + try { + appConfig.save(); + } catch (GeneralSecurityException | IOException e) { + // no-op + e.printStackTrace(); + } + break; + } + } +} +/** +* This class filters the Saleforce fields list +*/ + +class SforceFieldsFilter extends ViewerFilter { + private Text searchText; + public SforceFieldsFilter(Text search) { + super(); + this.searchText = search; + } + /** + * Returns whether the specified element passes this filter + * + * @param arg0 + * the viewer + * @param arg1 + * the parent element + * @param arg2 + * the element + * @return boolean + */ + @Override + public boolean select(Viewer arg0, Object arg1, Object arg2) { + + Field selectedField = (Field)arg2; + String fieldName = selectedField.getName(); + String fieldLabel = selectedField.getLabel(); + String searchText = this.searchText.getText(); + if (searchText != null && !searchText.isBlank()) { + searchText = searchText.toLowerCase(); + if ((fieldName != null && fieldName.toLowerCase().contains(searchText)) + || (fieldLabel != null && fieldLabel.toLowerCase().contains(searchText))) { + return true; + } else { + return false; + } + } + return true; + } } diff --git a/src/main/java/com/salesforce/dataloader/ui/MappingPage.java b/src/main/java/com/salesforce/dataloader/ui/MappingPage.java index ec6317177..d5b8ba02b 100644 --- a/src/main/java/com/salesforce/dataloader/ui/MappingPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/MappingPage.java @@ -30,7 +30,6 @@ import java.util.Map.Entry; import java.util.List; -import org.apache.log4j.Logger; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionAdapter; @@ -39,15 +38,14 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dyna.ObjectField; import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.mapping.LoadMapper; import com.salesforce.dataloader.ui.mapping.MappingContentProvider; import com.salesforce.dataloader.ui.mapping.MappingLabelProvider; import com.sforce.soap.partner.Field; -import com.sforce.soap.partner.FieldType; /** * Describe your class here. @@ -57,18 +55,12 @@ */ public class MappingPage extends LoadPage { - private final Controller controller; private TableViewer mappingTblViewer; - private final Logger logger = Logger.getLogger(MappingPage.class); private Map relatedFields; + private Label mappingLabel; public MappingPage(Controller controller) { - super(Labels.getString("MappingPage.title"), Labels.getString("MappingPage.titleMsg"), UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - // Set the description - setDescription(Labels.getString("MappingPage.description")); //$NON-NLS-1$ - this.controller = controller; - + super("MappingPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ } @Override @@ -87,16 +79,14 @@ public void createControl(Composite parent) { @Override public void widgetSelected(SelectionEvent event) { FileDialog dlg = new FileDialog(getShell(), SWT.OPEN); + dlg.setFilterExtensions(new String[] { "*.sdl" }); String filename = dlg.open(); - if (filename != null && !"".equals(filename)) { //$NON-NLS-1$ - Config config = controller.getConfig(); - config.setValue(Config.MAPPING_FILE, filename); + if (filename != null && !filename.isBlank()) { //$NON-NLS-1$ LoadMapper mapper = (LoadMapper)controller.getMapper(); - mapper.clearMap(); + mapper.clearMappings(); try { mapper.putPropertyFileMappings(filename); - updateMapping(); - packMappingColumns(); + updateMappingTblViewer(); } catch (MappingInitializationException e) { logger.error(Labels.getString("MappingPage.errorLoading"), e); //$NON-NLS-1$ UIUtils.errorMessageBox(getShell(), e); @@ -113,7 +103,7 @@ public void widgetSelected(SelectionEvent event) { public void widgetSelected(SelectionEvent event) { MappingDialog dlg = new MappingDialog(getShell(), controller, thisPage); dlg.setMapper((LoadMapper)controller.getMapper()); - dlg.setSforceFieldInfo(getFieldTypes()); + dlg.setUnmappedSobjectFields(autoMatchFieldsAndGetUnmappedFields()); dlg.open(); } }); @@ -124,16 +114,15 @@ public void widgetSelected(SelectionEvent event) { data.heightHint = 15; blankLabel2.setLayoutData(data); + Label sep3 = new Label(comp, SWT.HORIZONTAL | SWT.SEPARATOR); data = new GridData(GridData.FILL_HORIZONTAL); data.horizontalSpan = 2; sep3.setLayoutData(data); - Label mappingLabel = new Label(comp, SWT.NONE); - mappingLabel.setText(Labels.getString("MappingPage.currentBelow")); //$NON-NLS-1$ - data = new GridData(); + mappingLabel = new Label(comp, SWT.NONE); + data = new GridData(GridData.FILL_HORIZONTAL); data.horizontalSpan = 2; - data.heightHint = 20; mappingLabel.setLayoutData(data); // mapping field table viewer @@ -143,22 +132,19 @@ public void widgetSelected(SelectionEvent event) { // Set up the mapping table Table mappingTable = mappingTblViewer.getTable(); - data = new GridData(GridData.FILL_HORIZONTAL); - data.heightHint = 200; + data = new GridData(GridData.FILL_BOTH); data.horizontalSpan = 2; mappingTable.setLayoutData(data); // Add the first column - name - TableColumn tc = new TableColumn(mappingTable, SWT.LEFT); - tc.setText(Labels.getString("MappingPage.fileColumn")); //$NON-NLS-1$ - - //Add the second column - label - tc = new TableColumn(mappingTable, SWT.LEFT); - tc.setText(Labels.getString("MappingPage.name")); //$NON-NLS-1$ + TableColumn csvFieldColumn = new TableColumn(mappingTable, SWT.LEFT); + csvFieldColumn.setText(Labels.getString("MappingPage.fileColumn")); //$NON-NLS-1$ + //Add the second column - label + TableColumn sobjectFieldColumn = new TableColumn(mappingTable, SWT.LEFT); + sobjectFieldColumn.setText(Labels.getString("MappingPage.fieldName")); //$NON-NLS-1$ //update the model - updateMapping(); - + updateMappingTblViewer(); packMappingColumns(); // Turn on the header and the lines @@ -166,6 +152,7 @@ public void widgetSelected(SelectionEvent event) { mappingTable.setLinesVisible(true); setControl(comp); + setupPage(); } public void packMappingColumns() { @@ -176,12 +163,13 @@ public void packMappingColumns() { mappingTable.getColumn(i).pack(); } } + refreshMapping(); } /** * Responsible for updating the mapping model */ - public void updateMapping() { + public void updateMappingTblViewer() { // Set the table viewer's input if (mappingTblViewer != null) { mappingTblViewer.setInput(controller.getMapper()); @@ -192,31 +180,130 @@ public void updateMapping() { table.showItem(table.getItem(0)); } } + packMappingColumns(); + mappingTblViewer.getControl().pack(); + } + + public Field[] autoMatchFieldsAndGetUnmappedFields() { + Field[] sforceFields = getMappableSalesforceFields(); + if (sforceFields == null || sforceFields.length == 0) { + return sforceFields; + } + ArrayList fieldList = new ArrayList(Arrays.asList(sforceFields)); + //first match on name, then label + ListIterator iterator = fieldList.listIterator(); + Field sobjectField; + LoadMapper mapper = (LoadMapper)controller.getMapper(); + while (iterator.hasNext()) { + sobjectField = iterator.next(); + String sobjectFieldName = sobjectField.getName(); + String sobjectFieldLabel = sobjectField.getLabel(); + String daoColName = null; + + if (mapper.hasDaoColumn(sobjectFieldName)) { + daoColName = sobjectFieldName; + } else if (mapper.hasDaoColumn(sobjectFieldLabel)) { + daoColName = sobjectFieldLabel; + } + + if(daoColName != null) { + // don't overwrite the fields that already have been mapped + String prevMappedFieldName = mapper.getMapping(daoColName, false, true); + if(prevMappedFieldName == null || prevMappedFieldName.length() == 0) { + mapper.putMapping(daoColName, sobjectFieldName); + } + iterator.remove(); + } + } + return fieldList.toArray(new Field[fieldList.size()]); + } + + Field[] getMappableSalesforceFields() { + ArrayList mappableFieldList = new ArrayList(); + AppConfig appConfig = controller.getAppConfig(); + OperationInfo operation = appConfig.getOperationInfo(); + String extIdField = appConfig.getString(AppConfig.PROP_IDLOOKUP_FIELD); + if(extIdField == null) { + extIdField = ""; + } else { + extIdField = extIdField.toLowerCase(); + } + + for (Field field : getFieldTypes()) { + boolean isMappable = false; + switch (operation) { + case insert: + if (field.isCreateable()) { + isMappable = true; + } + break; + case delete: + if (controller.getAppConfig().isRESTAPIEnabled() + && Controller.getAPIMajorVersion() >= 61 + && controller.getAppConfig().getBoolean(AppConfig.PROP_DELETE_WITH_EXTERNALID) + && field.isIdLookup()) { + isMappable = true; + } + // do not break here. Continue to cover id field. + case undelete: + case hard_delete: + if (field.getType().toString().toLowerCase().equals("id")) { + isMappable = true; + } + break; + case upsert: + if (field.isUpdateable() || field.isCreateable() + // also add idLookup-Fields (such as Id, Name) IF they are used as extIdField in this upsert + // (no need to add them otherwise, if they are not updateable/createable) + || (field.isIdLookup() && extIdField.equals(field.getName().toLowerCase()))) { + isMappable = true; + } + break; + case update: + if (field.isUpdateable() || field.getType().toString().toLowerCase().equals("id")) { + isMappable = true; + } + break; + default: + throw new UnsupportedOperationException(); + } + if(isMappable) { + mappableFieldList.add(field); + } + } + + return mappableFieldList.toArray(new Field[mappableFieldList.size()]); } - public void refreshMapping() { + private void refreshMapping() { if (mappingTblViewer != null) { mappingTblViewer.refresh(); } + UIUtils.setTableColWidth(this.mappingTblViewer.getTable()); } public Field[] getFieldTypes() { Field[] result; - if (!controller.getConfig().getOperationInfo().isDelete()) { - Field[] fields = controller.getFieldTypes().getFields(); + Field[] fields = controller.getFieldTypes().getFields(); + if (controller.getAppConfig().getOperationInfo().isDelete()) { + ArrayList refFieldList = new ArrayList(); + for (Field field : fields) { + if (controller.getAppConfig().isRESTAPIEnabled() + && Controller.getAPIMajorVersion() >= 61 + && controller.getAppConfig().getBoolean(AppConfig.PROP_DELETE_WITH_EXTERNALID) + && field.isIdLookup()) { + refFieldList.add(field); + } else if (field.getType().toString().equalsIgnoreCase("id")) { + refFieldList.add(field); + } + } + result = refFieldList.toArray(new Field[1]); + } else { if(relatedFields != null) { result = addRelatedFields(fields); } else { result = fields; } - } else { - Field[] idFields = new Field[1]; - Field idField = new Field(); - idField.setName("Id"); - idField.setLabel("Id"); - idField.setType(FieldType.id); - idFields[0] = idField; - result = idFields; } return result; } @@ -233,18 +320,10 @@ public void setRelatedFields(Map fields) { * @param fields */ private Field[] addRelatedFields(Field[] fields) { - List relatedFieldList = new LinkedList(); + List relatedFieldList = new ArrayList(); for(Entry relatedFieldInfo : relatedFields.entrySet()) { - String relationshipName = relatedFieldInfo.getKey(); - Field relatedField = relatedFieldInfo.getValue(); - String mapFieldName = ObjectField.formatAsString(relationshipName, relatedField.getName()); - Field mapField = new Field(); - mapField.setName(mapFieldName); - mapField.setLabel(relationshipName + " " + relatedField.getLabel()); - mapField.setType(FieldType.reference); - mapField.setCreateable(relatedField.isCreateable()); - mapField.setUpdateable(relatedField.isUpdateable()); - relatedFieldList.add(mapField); + Field lookupField = relatedFieldInfo.getValue(); + relatedFieldList.add(lookupField); } relatedFieldList.addAll(Arrays.asList(fields)); return relatedFieldList.toArray(fields); @@ -255,18 +334,18 @@ private Field[] addRelatedFields(Field[] fields) { * @see com.salesforce.dataloader.ui.LoadPage#setupPage() */ @Override - boolean setupPage() { - try { - // clear mapping file - controller.getConfig().setValue(Config.MAPPING_FILE, ""); - controller.createMapper(); - updateMapping(); - packMappingColumns(); - return true; - } catch (MappingInitializationException e) { - UIUtils.errorMessageBox(getShell(), e); - logger.error("Could not initialize mapping page", e); - return false; + public boolean setupPagePostLogin() { + if (controller.getMapper() == null) { + return true; // further processing is not possible } + autoMatchFieldsAndGetUnmappedFields(); + updateMappingTblViewer(); + return true; + } + + @Override + public void setPageComplete() { + // no validations performed currently + setPageComplete(true); } } diff --git a/src/main/java/com/salesforce/dataloader/ui/OAuthBrowserListener.java b/src/main/java/com/salesforce/dataloader/ui/OAuthBrowserListener.java index 498a67316..78c9572b1 100644 --- a/src/main/java/com/salesforce/dataloader/ui/OAuthBrowserListener.java +++ b/src/main/java/com/salesforce/dataloader/ui/OAuthBrowserListener.java @@ -26,8 +26,10 @@ package com.salesforce.dataloader.ui; -import com.salesforce.dataloader.config.Config; -import org.apache.log4j.Logger; +import com.salesforce.dataloader.config.AppConfig; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.ProgressEvent; import org.eclipse.swt.browser.ProgressListener; @@ -37,24 +39,29 @@ * The common class for listening to browser events use in oauth flows. */ public abstract class OAuthBrowserListener implements ProgressListener { + private static Logger logger = DLLogManager.getLogger(AppConfig.class); protected final Browser browser; protected final Shell shell; - protected final Config config; + protected final AppConfig appConfig; private String reasonPhrase; private int statusCode; private boolean result; - public OAuthBrowserListener(Browser browser, Shell shell, Config config) { + public OAuthBrowserListener(Browser browser, Shell shell, AppConfig appConfig) { this.browser = browser; this.shell = shell; - this.config = config; + this.appConfig = appConfig; } @Override public abstract void changed(ProgressEvent progressEvent); - @Override - public abstract void completed(ProgressEvent progressEvent); + public void completed(ProgressEvent progressEvent) { + logger.debug("SWT Browser engine is " + + browser.evaluate("var ua = navigator.userAgent, tem, M = ua.match(/(opera|chrome|safari|firefox|msie|trident(?=\\/))\\/?\\s*(\\d+)/i) || []; if (/trident/i.test(M[1])) { tem = /\\brv[ :]+(\\d+)/g.exec(ua) || []; return 'IE ' + (tem[1] || ''); } if (M[1] === 'Chrome') { tem = ua.match(/\\b(OPR|Edge)\\/(\\d+)/); if (tem != null) return tem.slice(1).join(' ').replace('OPR', 'Opera'); } M = M[2] ? [M[1], M[2]] : [navigator.appName, navigator.appVersion, '-?']; if ((tem = ua.match(/version\\/(\\d+)/i)) != null) M.splice(1, 1, tem[1]); return M.join(' ');")); + logger.debug("SWT Browser engine's userAgent property is " + + browser.evaluate("return navigator.userAgent;")); + } public String getReasonPhrase() { return reasonPhrase; diff --git a/src/main/java/com/salesforce/dataloader/ui/OAuthFlow.java b/src/main/java/com/salesforce/dataloader/ui/OAuthFlow.java index 706fb2216..55bd0a83c 100644 --- a/src/main/java/com/salesforce/dataloader/ui/OAuthFlow.java +++ b/src/main/java/com/salesforce/dataloader/ui/OAuthFlow.java @@ -25,19 +25,21 @@ */ package com.salesforce.dataloader.ui; -import com.salesforce.dataloader.config.Config; -import org.apache.http.client.utils.URIBuilder; -import org.apache.log4j.Logger; + +import com.salesforce.dataloader.config.AppConfig; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.swt.SWT; import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Dialog; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; -import java.util.HashMap; -import java.util.Map; /** * OAuthFlow is basic instrumentation of delegating authentication to an external web browser using OAuth2 @@ -45,14 +47,14 @@ * by individuals using connected apps as it requires storing a secret which cannot be done for the normal login */ public abstract class OAuthFlow extends Dialog { - protected static Logger logger = Logger.getLogger(OAuthFlow.class); - protected final Config config; + protected static Logger logger = DLLogManager.getLogger(OAuthFlow.class); + protected final AppConfig appConfig; private String reasonPhrase; private int statusCode; - public OAuthFlow(Shell parent, Config config) { + public OAuthFlow(Shell parent, AppConfig appConfig) { super(parent); - this.config = config; + this.appConfig = appConfig; } public String getReasonPhrase() { @@ -66,16 +68,23 @@ public int getStatusCode() { public boolean open() throws UnsupportedEncodingException { // Create the dialog window Display display = getParent().getDisplay(); - Shell shell = new Shell(getParent(), SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.FILL); - Grid12 grid = new Grid12(shell, 30, 600); + Shell shell = new Shell(getParent(), SWT.RESIZE | SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.FILL); + GridLayout layout = new GridLayout(1, false); + shell.setLayout(layout); // Create the web browser Browser browser = new Browser(shell, SWT.NONE); - browser.setLayoutData(grid.createCell(12)); - - OAuthBrowserListener listener = getOAuthBrowserListener(shell, browser, config); + GridData data = new GridData(); + data.horizontalAlignment = SWT.FILL; + data.grabExcessHorizontalSpace = true; + data.grabExcessVerticalSpace = true; + data.widthHint = 400; + data.heightHint = 600; + browser.setLayoutData(data); + + OAuthBrowserListener listener = getOAuthBrowserListener(shell, browser, appConfig); browser.addProgressListener(listener); - browser.setUrl(getStartUrl(config)); + browser.setUrl(getStartUrl(appConfig)); shell.pack(); shell.open(); @@ -88,20 +97,9 @@ public boolean open() throws UnsupportedEncodingException { reasonPhrase = listener.getReasonPhrase(); statusCode = listener.getStatusCode(); - - return listener.getResult(); } - protected abstract OAuthBrowserListener getOAuthBrowserListener(Shell shell, Browser browser, Config config); - - public abstract String getStartUrl(Config config) throws UnsupportedEncodingException; - - public static Map getQueryParameters(String url) throws URISyntaxException { - url = url.replace("#","?"); - Map params = new HashMap<>(); - new URIBuilder(url).getQueryParams().stream().forEach(kvp -> params.put(kvp.getName(), kvp.getValue())); - return params; - } - + protected abstract OAuthBrowserListener getOAuthBrowserListener(Shell shell, Browser browser, AppConfig appConfig); + public abstract String getStartUrl(AppConfig appConfig) throws UnsupportedEncodingException; } diff --git a/src/main/java/com/salesforce/dataloader/ui/OAuthLoginControl.java b/src/main/java/com/salesforce/dataloader/ui/OAuthLoginControl.java new file mode 100644 index 000000000..c8a4e36be --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/OAuthLoginControl.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.model.LoginCriteria; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; + +import java.util.ArrayList; + +/** + * Login default control is the oauth login + */ +public class OAuthLoginControl extends Composite { + private final Button loginButton; + private LoginPage loginPage; + protected final Combo environment; + protected final AuthenticationRunner authRunner; + protected final Label loginStatusLabel; + + public OAuthLoginControl(Composite parent, int style, LoginPage loginPage, AuthenticationRunner authRunner) { + super(parent, style); + this.authRunner = authRunner; + this.loginPage = loginPage; + + Grid12 grid = new Grid12(this, 40, false, true); + + grid.createLabel(4, Labels.getString("LoginPage.environment")); + ArrayList environments = authRunner.getConfig().getStrings(AppConfig.PROP_SERVER_ENVIRONMENTS); + environment = grid.createCombo(6, SWT.DROP_DOWN | SWT.BORDER, environments); + String currentEnvironment = authRunner.getConfig().getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT); + if (environments.contains(currentEnvironment)) { + environment.setText(currentEnvironment); + } + + grid.createPadding(2); + + @SuppressWarnings("unused") + Label emptyLabel = grid.createLabel(8, ""); + loginButton = grid.createButton(2, SWT.PUSH | SWT.FILL | SWT.FLAT, Labels.getString("LoginPage.login")); + loginButton.addListener(SWT.Selection, this::loginButton_Clicked); + grid.createPadding(2); + + loginStatusLabel = grid.createLeftLabel(10, "\n\n\n"); + } + + protected void loginButton_Clicked(Event event) { + LoginCriteria criteria = new LoginCriteria(LoginCriteria.OAuthLogin); + criteria.setEnvironment(environment.getText()); + authRunner.login(criteria, this::setLoginStatus); + } + private void setLoginStatus(String statusStr) { + if (this.loginPage.controller.isLoggedIn()) { + loginButton.setEnabled(false); + } + loginStatusLabel.setText(statusStr); + loginPage.setPageComplete(); + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/OAuthLoginFromBrowserFlow.java b/src/main/java/com/salesforce/dataloader/ui/OAuthLoginFromBrowserFlow.java new file mode 100644 index 000000000..bf844d2ae --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/OAuthLoginFromBrowserFlow.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.util.OAuthBrowserLoginRunner; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.Clipboard; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Dialog; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Shell; + +import java.io.UnsupportedEncodingException; + +/** + * the oauth token flow. this is normally used for client to server where the client is not a secured environment + * as it does not involve a secret. We use this as our standard login for SF oauth. The disadvantage to this flow is + * it prompts for authentication and authorization everytime. + */ +public class OAuthLoginFromBrowserFlow extends Dialog { + protected static Logger logger = DLLogManager.getLogger(OAuthLoginFromBrowserFlow.class); + protected final AppConfig appConfig; + + public OAuthLoginFromBrowserFlow(Shell parent, AppConfig appConfig) { + super(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.FILL); + this.appConfig = appConfig; + } + + public boolean open() throws UnsupportedEncodingException { + final String verificationURLStr; + final OAuthBrowserLoginRunner loginRunner; + try { + loginRunner = new OAuthBrowserLoginRunner(appConfig, true); + verificationURLStr = loginRunner.getVerificationURLStr(); + } catch (Exception ex) { + logger.error(ex.getMessage()); + return false; + } + // Create the dialog window + Display display = getParent().getDisplay(); + int style = getParent().getStyle(); + Shell shell = new Shell(getParent(), style | SWT.APPLICATION_MODAL); + Font f = shell.getFont(); + FontData[] farr = f.getFontData(); + FontData fd = farr[0]; + fd.setStyle(SWT.BOLD); + Font boldFont = new Font(Display.getCurrent(), fd); + shell.setText(Labels.getString("OAuthInBrowser.title")); + + Composite container = new Composite(shell, SWT.NONE); + GridLayout containerLayout = new GridLayout(1, false); + container.setLayout(containerLayout); + shell.setLayout(new FillLayout()); + + Composite infoComp = new Composite(container, SWT.NONE); + GridData data = new GridData(GridData.FILL_HORIZONTAL | GridData.VERTICAL_ALIGN_BEGINNING | GridData.HORIZONTAL_ALIGN_CENTER); + data.heightHint = 150; + data.widthHint = 600; + GridLayout layout = new GridLayout(2, false); + layout.marginHeight = 0; + layout.marginWidth = 0; + layout.verticalSpacing = 0; + infoComp.setLayout(layout); + infoComp.setLayoutData(data); + infoComp.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + + Label label = new Label(infoComp, SWT.RIGHT); + label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + label.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); + label.setText(Labels.getString("OAuthInBrowser.authStep1Title")); + label.setFont(boldFont); + + label = new Label(infoComp, SWT.WRAP); + label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + label.setText(Labels.getString("OAuthInBrowser.authStep1Content")); + + label = new Label(infoComp, SWT.WRAP); + label = new Label(infoComp, SWT.WRAP); + + label = new Label(infoComp, SWT.RIGHT); + label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + label.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); + label.setText(Labels.getString("OAuthInBrowser.authStep2Title")); + label.setFont(boldFont); + + label = new Label(infoComp, SWT.WRAP); + label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + label.setText(Labels.getFormattedString("OAuthInBrowser.authStep2Content", loginRunner.getUserCode())); + + label = new Label(infoComp, SWT.WRAP); + label = new Label(infoComp, SWT.WRAP); + + label = new Label(infoComp, SWT.RIGHT); + label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + label.setLayoutData(new GridData(GridData.VERTICAL_ALIGN_BEGINNING)); + label.setText(Labels.getString("OAuthInBrowser.authStep3Title")); + label.setFont(boldFont); + + label = new Label(infoComp, SWT.WRAP); + label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); + label.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); + label.setText(Labels.getString("OAuthInBrowser.authStep3Content")); + + + Composite contentComp = new Composite(container, SWT.NONE); + data = new GridData(GridData.FILL_BOTH); + contentComp.setLayoutData(data); + GridLayout contentLayout = new GridLayout(3, false); + contentLayout.verticalSpacing = 10; + contentComp.setLayout(contentLayout); + + label = new Label(contentComp, SWT.RIGHT); + label.setFont(new Font(Display.getCurrent(), fd)); + label.setText(Labels.getString("OAuthInBrowser.verificationURL")); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + label.setLayoutData(data); + Link link = new Link(contentComp, SWT.END); + link.setText(""+verificationURLStr+""); + link.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + URLUtil.openURL(e.text); + } + }); + final Clipboard clipboard = new Clipboard(display); + Button copy = new Button(contentComp, SWT.PUSH); + copy.setText(Labels.getString("OAuthInBrowser.copyToClipboardButton")); + copy.addListener(SWT.Selection, new Listener() { + public void handleEvent(Event e) { + String textData = verificationURLStr; + TextTransfer textTransfer = TextTransfer.getInstance(); + clipboard.setContents(new Object[] { textData }, + new Transfer[] { textTransfer }); + } + }); + + + shell.pack(); + shell.open(); + + + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) { + display.sleep(); + } + if (loginRunner.isLoginProcessCompleted()) { + shell.close(); + break; + } + } + return loginRunner.getLoginStatus() == OAuthBrowserLoginRunner.LoginStatus.SUCCESS; + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/OAuthSecretFlow.java b/src/main/java/com/salesforce/dataloader/ui/OAuthSecretFlow.java index 907380eb2..640b242d5 100644 --- a/src/main/java/com/salesforce/dataloader/ui/OAuthSecretFlow.java +++ b/src/main/java/com/salesforce/dataloader/ui/OAuthSecretFlow.java @@ -25,30 +25,21 @@ */ package com.salesforce.dataloader.ui; -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.salesforce.dataloader.client.SimplePost; -import com.salesforce.dataloader.client.SimplePostFactory; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.exception.ParameterLoadException; -import com.salesforce.dataloader.model.OAuthToken; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URIBuilder; -import org.apache.http.message.BasicNameValuePair; -import org.apache.log4j.Logger; +import com.salesforce.dataloader.oauth.OAuthSecretFlowUtil; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.ProgressEvent; import org.eclipse.swt.widgets.Shell; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.util.Map; -import java.util.Optional; /** * the oauth authorization_code. this is normally reserved for server to server communications as it involves storing @@ -56,34 +47,26 @@ * it prompts for authentication but not authorization (once it's been authorized at least once). */ public class OAuthSecretFlow extends OAuthFlow { - protected static Logger logger = Logger.getLogger(OAuthFlow.class); + protected static Logger logger = DLLogManager.getLogger(OAuthSecretFlow.class); - public OAuthSecretFlow(Shell parent, Config config) { - super(parent, config); + public OAuthSecretFlow(Shell parent, AppConfig appConfig) { + super(parent, appConfig); } @Override - protected OAuthBrowserListener getOAuthBrowserListener(Shell shell, Browser browser, Config config) { - return new OAuthSecretBrowserListener(browser, shell, config); + protected OAuthBrowserListener getOAuthBrowserListener(Shell shell, Browser browser, AppConfig appConfig) { + return new OAuthSecretBrowserListener(browser, shell, appConfig); } @Override - public String getStartUrl(Config config) throws UnsupportedEncodingException { - return getStartUrlImpl(config); - } - - //SWT Components are not testable :( - public static String getStartUrlImpl(Config config) throws UnsupportedEncodingException { - return config.getString(Config.OAUTH_SERVER) + - "/services/oauth2/authorize?response_type=code&display=popup&client_id=" + - config.getString(Config.OAUTH_CLIENTID) + "&redirect_uri=" + - URLEncoder.encode(config.getString(Config.OAUTH_REDIRECTURI), "UTF-8"); + public String getStartUrl(AppConfig appConfig) throws UnsupportedEncodingException { + return OAuthSecretFlowUtil.getStartUrlImpl(appConfig); } public static class OAuthSecretBrowserListener extends OAuthBrowserListener { - public OAuthSecretBrowserListener(Browser browser, Shell shell, Config config) { - super(browser, shell, config); + public OAuthSecretBrowserListener(Browser browser, Shell shell, AppConfig appConfig) { + super(browser, shell, appConfig); } @Override @@ -93,12 +76,13 @@ public void changed(ProgressEvent progressEvent) { @Override public void completed(ProgressEvent progressEvent) { + super.completed(progressEvent); String url = browser.getUrl(); try { - String code = handleInitialUrl(url); + String code = OAuthSecretFlowUtil.handleInitialUrl(url); if (code != null) { - SimplePost client = handleSecondPost(code, config); + SimplePost client = OAuthSecretFlowUtil.handleSecondPost(code, appConfig); setReasonPhrase(client.getReasonPhrase()); setStatusCode(client.getStatusCode()); setResult(client.isSuccessful()); @@ -109,44 +93,5 @@ public void completed(ProgressEvent progressEvent) { doSimpleErrorHandling(url, e, logger); } } - - public static SimplePost handleSecondPost(String code, Config config) throws IOException, ParameterLoadException { - String server = config.getString(Config.OAUTH_SERVER) + "/services/oauth2/token"; - SimplePost client = SimplePostFactory.getInstance(config, server, - new BasicNameValuePair("grant_type", "authorization_code"), - new BasicNameValuePair("code", code), - new BasicNameValuePair("client_id", config.getString(Config.OAUTH_CLIENTID)), - new BasicNameValuePair("client_secret", config.getString(Config.OAUTH_CLIENTSECRET)), - new BasicNameValuePair("redirect_uri", config.getString(Config.OAUTH_REDIRECTURI)) - ); - client.post(); - - if (client.isSuccessful()) { - - StringBuilder builder = new StringBuilder(); - BufferedReader in = new BufferedReader(new InputStreamReader(client.getInput(), "UTF-8")); - for (int c = in.read(); c != -1; c = in.read()) { - builder.append((char) c); - } - - String jsonTokenResult = builder.toString(); - Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - OAuthToken token = gson.fromJson(jsonTokenResult, OAuthToken.class); - config.setValue(Config.OAUTH_ACCESSTOKEN, token.getAccessToken()); - config.setValue(Config.OAUTH_REFRESHTOKEN, token.getRefreshToken()); - config.setValue(Config.ENDPOINT, token.getInstanceUrl()); - - } - - return client; - } - - public static String handleInitialUrl(String url) throws URISyntaxException { - Map queryParameters = getQueryParameters(url); - return queryParameters.get("code"); - } - } } diff --git a/src/main/java/com/salesforce/dataloader/ui/OAuthTokenFlow.java b/src/main/java/com/salesforce/dataloader/ui/OAuthTokenFlow.java index 857c386cc..734bc0275 100644 --- a/src/main/java/com/salesforce/dataloader/ui/OAuthTokenFlow.java +++ b/src/main/java/com/salesforce/dataloader/ui/OAuthTokenFlow.java @@ -26,19 +26,16 @@ package com.salesforce.dataloader.ui; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.model.OAuthToken; -import org.eclipse.swt.SWT; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.oauth.OAuthFlowUtil; + import org.eclipse.swt.browser.Browser; import org.eclipse.swt.browser.ProgressEvent; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.MessageBox; import org.eclipse.swt.widgets.Shell; import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.util.Map; + /** * the oauth token flow. this is normally used for client to server where the client is not a secured environment @@ -46,30 +43,23 @@ * it prompts for authentication and authorization everytime. */ public class OAuthTokenFlow extends OAuthFlow { - public OAuthTokenFlow(Shell parent, Config config) { - super(parent, config); + public OAuthTokenFlow(Shell parent, AppConfig appConfig) { + super(parent, appConfig); } @Override - protected OAuthBrowserListener getOAuthBrowserListener(Shell shell, Browser browser, Config config) { - return new OAuthTokenBrowserLister(shell, browser, config); + protected OAuthBrowserListener getOAuthBrowserListener(Shell shell, Browser browser, AppConfig appConfig) { + return new OAuthTokenBrowserLister(shell, browser, appConfig); } @Override - public String getStartUrl(Config config) throws UnsupportedEncodingException { - return getStartUrlImpl(config); - } - - public static String getStartUrlImpl(Config config) throws UnsupportedEncodingException { - return config.getString(Config.OAUTH_SERVER) + - "/services/oauth2/authorize?response_type=token&display=popup&client_id=" + - config.getString(Config.OAUTH_CLIENTID) + "&redirect_uri=" + - URLEncoder.encode(config.getString(Config.OAUTH_REDIRECTURI), "UTF-8"); + public String getStartUrl(AppConfig appConfig) throws UnsupportedEncodingException { + return OAuthFlowUtil.getStartUrlImpl(appConfig); } public static class OAuthTokenBrowserLister extends OAuthBrowserListener { - public OAuthTokenBrowserLister(Shell shell, Browser browser, Config config) { - super(browser, shell, config); + public OAuthTokenBrowserLister(Shell shell, Browser browser, AppConfig appConfig) { + super(browser, shell, appConfig); } @Override @@ -79,8 +69,9 @@ public void changed(ProgressEvent progressEvent) { @Override public void completed(ProgressEvent progressEvent) { + super.completed(progressEvent); try { - boolean handled = handleCompletedUrl(browser.getUrl(), config); + boolean handled = OAuthFlowUtil.handleCompletedUrl(browser.getUrl(), appConfig); if (handled) { setResult(true); shell.close(); @@ -90,50 +81,5 @@ public void completed(ProgressEvent progressEvent) { doSimpleErrorHandling(browser.getUrl(), e, logger); } } - - public static boolean handleCompletedUrl(String url, Config config) throws URISyntaxException { - Map params = getQueryParameters(url); - - if (params.containsKey("access_token")){ - //we don't use most of this but I still like to track what we should get - OAuthToken token = new OAuthToken(); - token.setInstanceUrl(params.get("instance_url")); - token.setId(params.get("id")); - token.setAccessToken(params.get("access_token")); - - //optional parameters - if (params.containsKey("refresh_token")) { - token.setRefreshToken(params.get("refresh_token")); - } - - //currently unused parameters - if (params.containsKey("scope")) { - token.setScope(params.get("scope")); - } - if (params.containsKey("signature")) { - token.setSignature(params.get("signature")); - } - if (params.containsKey("token_type")) { - token.setTokenType(params.get("token_type")); - } - if (params.containsKey("issued_at")) { - String issued_at = params.get("issued_at"); - if (issued_at != null && !issued_at.equals("")) { - token.setIssuedAt(new Long(issued_at)); - } - } - - - config.setValue(Config.OAUTH_ACCESSTOKEN, token.getAccessToken()); - config.setValue(Config.OAUTH_REFRESHTOKEN, token.getRefreshToken()); - config.setValue(Config.ENDPOINT, token.getInstanceUrl()); - - return true; - } - - return false; - } } - - } diff --git a/src/main/java/com/salesforce/dataloader/ui/OperationPage.java b/src/main/java/com/salesforce/dataloader/ui/OperationPage.java new file mode 100644 index 000000000..22e02e6c3 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/OperationPage.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import java.io.IOException; +import java.security.GeneralSecurityException; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.exception.ParameterLoadException; + +/** +* This is the base class for the LoadWizard and ExtractionWizard ui pages. Allows navigation to be done dynamically by forcing setupPage to +* be implemented by each wizard page +* +*/ +public abstract class OperationPage extends WizardPage { + + /** + * @param pageName + * @param title + * @param titleImage + * + */ + protected final Controller controller; + protected final Logger logger; + + public OperationPage(String name, Controller controller) { + super(name); + this.setTitle(Labels.getString(getClass().getSimpleName() + ".title")); + this.setImageDescriptor(UIUtils.getImageRegistry().getDescriptor("logo")); + this.controller = controller; + this.logger = DLLogManager.getLogger(this.getClass()); + this.setPageComplete(false); + } + + public boolean setupPage() { + // Set the description + String description = Labels.getString(this.getClass().getSimpleName() + ".description"); + this.setDescription(description); + this.getShell().addListener(SWT.Resize, this::persistedWizardShellBoundsChanged); + this.getShell().addListener(SWT.Move, this::persistedWizardShellBoundsChanged); + + boolean success = true; + if (this.controller.isLoggedIn()) { + success = setupPagePostLogin(); + if (success) { + String message = this.getConfigInfo() + "\n" + this.controller.getAPIInfo(); + this.setMessage(message); + Control[] controls = this.getShell().getChildren(); + for (Control control : controls) { + if (control instanceof Composite) { + GridData data = new GridData(); + data.verticalSpan = GridData.FILL_VERTICAL; + data.grabExcessVerticalSpace = true; + control.setLayoutData(data); + + controls = ((Composite)control).getChildren(); + // get the first Composite among children + break; + } + } + + for (Control ctl : controls) { + // Tracer to see if extra vertical gap is removed + // ctl.setBackground(new Color(200, 0, 0)); + if (ctl instanceof Composite) { + Composite comp = (Composite)ctl; + Control[] children = comp.getChildren(); + for (Control child : children) { + GridData data = (GridData) child.getLayoutData(); + if (data == null) { + data = new GridData(); + } + data.verticalSpan = GridData.FILL_VERTICAL; + data.grabExcessVerticalSpace = true; + child.setLayoutData(data); + } + break; + } + } + } + } + + return success; + } + + public abstract void setPageComplete(); + protected abstract boolean setupPagePostLogin(); + protected abstract String getConfigInfo(); + + /** + * Need to subclass this function to prevent the getNextPage() function being called before the button is clicked. + */ + @Override + public boolean canFlipToNextPage() { + return isPageComplete(); + } + + // concrete subclasses must override this method if they allow Finish operation + public boolean finishAllowed() { + return false; + } + + public IWizardPage getNextPage() { + OperationPage nextPage = (OperationPage)super.getNextPage(); + if (nextPage != null) { + nextPage = nextPage.getNextPageOverride(); + } + return nextPage; + } + + protected OperationPage getNextPageOverride() { + return this; + } + + public IWizardPage getPreviousPage() { + OperationPage prevPage = (OperationPage)super.getPreviousPage(); + if (prevPage != null) { + prevPage = prevPage.getPreviousPageOverride(); + } + return prevPage; + } + + protected OperationPage getPreviousPageOverride() { + return this; + } + + private static boolean ignoreBoundsChanges = false; + private static final int IGNORE_BOUNDS_CHANGE_AMOUNT = 10; + protected static void setIgnoreBoundsChanges(boolean ignore) { + ignoreBoundsChanges = ignore; + } + + private void persistedWizardShellBoundsChanged(Event event) { + switch (event.type) { + case SWT.Resize: + case SWT.Move: + if (!this.getShell().isVisible() || ignoreBoundsChanges) { + return; + } + AppConfig appConfig = controller.getAppConfig(); + Rectangle shellBounds = this.getShell().getBounds(); + try { + int currentXOffset = appConfig.getInt(AppConfig.PROP_WIZARD_X_OFFSET); + if (Math.abs(currentXOffset - shellBounds.x) > IGNORE_BOUNDS_CHANGE_AMOUNT) { + appConfig.setValue(AppConfig.PROP_WIZARD_X_OFFSET, shellBounds.x); + } + int currentYOffset = appConfig.getInt(AppConfig.PROP_WIZARD_Y_OFFSET); + if (Math.abs(currentYOffset - shellBounds.x) > IGNORE_BOUNDS_CHANGE_AMOUNT) { + appConfig.setValue(AppConfig.PROP_WIZARD_Y_OFFSET, shellBounds.y); + } + int currentWidth = appConfig.getInt(AppConfig.PROP_WIZARD_WIDTH); + if (Math.abs(currentWidth - shellBounds.x) > IGNORE_BOUNDS_CHANGE_AMOUNT) { + appConfig.setValue(AppConfig.PROP_WIZARD_WIDTH, shellBounds.width); + } + int currentHeight = appConfig.getInt(AppConfig.PROP_WIZARD_HEIGHT); + if (Math.abs(currentHeight - shellBounds.x) > IGNORE_BOUNDS_CHANGE_AMOUNT) { + appConfig.setValue(AppConfig.PROP_WIZARD_HEIGHT, shellBounds.height); + } + } catch (ParameterLoadException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + try { + appConfig.save(); + } catch (GeneralSecurityException | IOException e) { + // no-op + e.printStackTrace(); + } + break; + } + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/SettingsHelpDialog.java b/src/main/java/com/salesforce/dataloader/ui/SettingsHelpDialog.java new file mode 100644 index 000000000..b9cee40b7 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/SettingsHelpDialog.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import org.eclipse.jface.resource.JFaceColors; +import org.eclipse.swt.SWT; +import org.eclipse.swt.browser.Browser; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.ConfigPropertyMetadata; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.exception.ParameterLoadException; + +public class SettingsHelpDialog extends BaseDialog { + private Shell dialogShell; + + public SettingsHelpDialog(Shell parent, Controller controller) { + super(parent, controller); + } + + @Override + protected void createContents(Shell shell) { + // Create the dialog window + dialogShell = shell; + GridLayout layout = new GridLayout(1, false); + dialogShell.setLayout(layout); + GridData data = new GridData(GridData.FILL_BOTH); + data.grabExcessHorizontalSpace = true; + data.grabExcessVerticalSpace = true; + int widthHint = 600; + int heightHint = 600; + try { + widthHint = AppConfig.getCurrentConfig().getInt(AppConfig.PROP_WIZARD_WIDTH); + heightHint = AppConfig.getCurrentConfig().getInt(AppConfig.PROP_WIZARD_HEIGHT); + } catch (ParameterLoadException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + data = new GridData(); + data.grabExcessHorizontalSpace = true; + data.grabExcessVerticalSpace = true; + data.widthHint = widthHint; + data.heightHint = heightHint; + dialogShell.setLayoutData(data); + + //display salesforce logo + Label titleImage = new Label(shell, SWT.CENTER); + Color background = JFaceColors.getBannerBackground(shell.getDisplay()); + shell.setBackground(background); + titleImage.setBackground(background); + titleImage.setImage(UIUtils.getImageRegistry().get("logo")); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + titleImage.setLayoutData(data); + + // Create the web browser + Browser browser = new Browser(dialogShell, SWT.NONE); + data = new GridData(GridData.FILL_HORIZONTAL); + data.grabExcessHorizontalSpace = true; + data.grabExcessVerticalSpace = true; + if (heightHint > 100) { + data.heightHint = heightHint - 100; + } + browser.setLayoutData(data); + browser.setText(Labels.getString("SettingsHelpDialog.settingsHelp")); + + String fullPathOfPropsCSV = ConfigPropertyMetadata.getFullPathToPropsFile(getController().getAppConfig()); + Link showPropertiesCSVLink = new Link(dialogShell, SWT.NONE); + showPropertiesCSVLink.setText(Labels.getFormattedString("SettingsHelpDialog.getPropsCSV", fullPathOfPropsCSV)); + showPropertiesCSVLink.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent arg0) { + } + @Override + public void mouseDown(MouseEvent arg0) { + CSVViewerDialog dlg = new CSVViewerDialog(getParent(), getController(), true, true); + dlg.setNumberOfRows(200); + dlg.setFileName(fullPathOfPropsCSV); + try { + dlg.open(); + } catch (DataAccessObjectInitializationException e) { + UIUtils.errorMessageBox(getParent(), e); + } + } + @Override + public void mouseUp(MouseEvent arg0) { + } + }); + + // vertical spacer + new Label(dialogShell, SWT.NONE); + + Button ok = new Button(dialogShell, SWT.PUSH | SWT.FLAT); + ok.setText(Labels.getString("UI.ok")); //$NON-NLS-1$ + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + data.widthHint = 75; + ok.setLayoutData(data); + ok.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + dialogShell.close(); + } + }); + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/SettingsPage.java b/src/main/java/com/salesforce/dataloader/ui/SettingsPage.java deleted file mode 100644 index 3e062b0df..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/SettingsPage.java +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.controller.Controller; -import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Event; - -/** - * Describe your class here. - * - * @author Lexi Viripaeff - * @since 6.0 - */ -public class SettingsPage extends WizardPage { - - private final Controller controller; - - private AuthenticationRunner authenticator; - private LoginDefaultControl defaultControl; - private LoginStandardControl standardControl; - private LoginAdvancedControl advancedControl; - private Grid12 grid; - private Composite control; - - public SettingsPage(Controller controller) { - super(Labels.getString("SettingsPage.title"), Labels.getString("SettingsPage.titleMsg"), UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - this.controller = controller; - - setPageComplete(false); - - // Set the description - setDescription(Labels.getString("SettingsPage.enterUsernamePassword")); //$NON-NLS-1$ - - - } - - @Override - public void createControl(Composite parent) { - getShell().setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - - Config config = controller.getConfig(); - control = new Composite(parent, SWT.FILL); - grid = new Grid12(control, 40); - authenticator = new AuthenticationRunner(getShell(), config, controller, this::authenticationCompleted); - - Button[] layouts = new Button[3]; - grid.createPadding(2); - layouts[0] = grid.createButton(2, SWT.RADIO, Labels.getString("SettingsPage.loginDefault")); - layouts[1] = grid.createButton(4, SWT.RADIO, Labels.getString("SettingsPage.loginStandard")); - layouts[2] = grid.createButton(2, SWT.RADIO, Labels.getString("SettingsPage.loginAdvanced")); - grid.createPadding(2); - - defaultControl = new LoginDefaultControl(control, SWT.FILL, authenticator); - defaultControl.setLayoutData(grid.createCell(12)); - standardControl = new LoginStandardControl(control, SWT.FILL, authenticator); - standardControl.setLayoutData(grid.createCell(12)); - advancedControl = new LoginAdvancedControl(control, SWT.FILL, authenticator); - advancedControl.setLayoutData(grid.createCell(12)); - - setControl(control); - - layouts[0].addListener(SWT.Selection, this::selectDefault); - layouts[1].addListener(SWT.Selection, this::selectStandard); - layouts[2].addListener(SWT.Selection, this::selectAdvanced); - - //turn off oauth options if no configured environments found - if (config.getStrings(Config.OAUTH_ENVIRONMENTS).size() > 0) { - layouts[0].setSelection(true); - selectDefault(null); - } else { - grid.hide(layouts[0]); - layouts[1].setSelection(true); - selectStandard(null); - } - if (!config.getBoolean(Config.SFDC_INTERNAL)){ - grid.hide(layouts[2]); - if (!layouts[0].getVisible()){ - //no options other than standard so don't present them - grid.hide(layouts[1]); - } - } - } - /** - * Loads DataSelectionPage. To be overridden by subclasses for special behavior. - * - * @param controller - */ - protected void loadDataSelectionPage(Controller controller) { - DataSelectionPage selection = (DataSelectionPage)getWizard().getPage(Labels.getString("DataSelectionPage.data")); //$NON-NLS-1$ - if(selection.setupPage()) { - setPageComplete(true); - } else { - setPageComplete(false); - } - } - - /** - * Need to subclass this function to prevent the getNextPage() function being called before the button is clicked. - */ - @Override - public boolean canFlipToNextPage() { - return isPageComplete(); - } - - /** - * Returns the next page, login. - * - * @return IWizardPage - */ - @Override - public IWizardPage getNextPage() { - return super.getNextPage(); - } - - public static boolean isNeeded(Controller controller) { - return (!controller.loginIfSessionExists() || controller.getEntityDescribes() == null || controller - .getEntityDescribes().isEmpty()); - } - - private void authenticationCompleted(Boolean success){ - if (success){ - loadDataSelectionPage(controller); - } - else{ - setPageComplete(false); - } - } - - private void selectAdvanced(Event event) { - show(advancedControl); - } - - private void selectStandard(Event event) { - show(standardControl); - } - - private void selectDefault(Event event) { - show(defaultControl); - } - - private void show(Composite showControl) { - grid.hide(defaultControl); - grid.hide(standardControl); - grid.hide(advancedControl); - grid.show(showControl); - - control.layout(false); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/UIUtils.java b/src/main/java/com/salesforce/dataloader/ui/UIUtils.java index 56ca85e86..b70b34bdb 100644 --- a/src/main/java/com/salesforce/dataloader/ui/UIUtils.java +++ b/src/main/java/com/salesforce/dataloader/ui/UIUtils.java @@ -26,30 +26,30 @@ package com.salesforce.dataloader.ui; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Arrays; -import java.util.List; +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; +import org.eclipse.jface.dialogs.IDialogConstants; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.resource.ImageRegistry; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.widgets.*; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; -import com.salesforce.dataloader.action.OperationInfo; +import java.util.Arrays; +import java.util.List; public class UIUtils { private static ImageRegistry image_registry; - public static URL newURL(String url_name) { - try { - return new URL(url_name); - } catch (MalformedURLException e) { - throw new RuntimeException("Malformed URL " + url_name, e); - } - } - public static synchronized ImageRegistry getImageRegistry() { if (image_registry == null) { @@ -60,7 +60,7 @@ public static synchronized ImageRegistry getImageRegistry() { image_registry.put("sfdc_icon", ImageDescriptor.createFromURL(baseClass.getClassLoader().getResource("img/icons/icon_32x32.png"))); - image_registry.put("logo", + image_registry.put("logo", ImageDescriptor.createFromURL(baseClass.getClassLoader().getResource("img/icons/icon_128x128.png"))); image_registry.put("title_logo", ImageDescriptor.createFromURL(baseClass.getClassLoader().getResource("img/dataloader-title-logo.png"))); @@ -69,11 +69,11 @@ public static synchronized ImageRegistry getImageRegistry() { for (OperationInfo info : OperationInfo.values()) { if (image_registry.get(info.getIconName()) == null) - image_registry.put(info.getIconName(), + image_registry.put(info.getIconName(), ImageDescriptor.createFromURL(baseClass.getClassLoader().getResource(info.getIconLocation()))); } - image_registry.put("downArrow", + image_registry.put("downArrow", ImageDescriptor.createFromURL(baseClass.getClassLoader().getResource("img/downArrow.gif"))); } @@ -102,8 +102,8 @@ public static int infoMessageBox(Shell shell, String message) { } private static int messageBox(Shell shell, String title, int uiProps, String message) { - MessageBox mb = new MessageBox (shell, uiProps); - if(title != null && title.length() > 0) { + MessageBox mb = new MessageBox(shell, uiProps); + if (title != null && title.length() > 0) { mb.setText(title); } mb.setMessage(String.valueOf(message)); @@ -111,17 +111,69 @@ private static int messageBox(Shell shell, String title, int uiProps, String mes } /** - * @param combo - * @param itemList * @return Array of combo items */ public static String[] setComboItems(Combo combo, List itemList, String defaultItemText) { String[] itemArray = itemList.toArray(new String[itemList.size()]); Arrays.sort(itemArray); combo.setItems(itemArray); - if(defaultItemText != null && defaultItemText.length() > 0) { + if (defaultItemText != null && defaultItemText.length() > 0) { combo.setText(defaultItemText); } return itemArray; } + + + public static int getControlWidth(Control control) { + GC gc = new GC(control); + gc.setFont(control.getFont()); + FontMetrics fontMetrics = gc.getFontMetrics(); + gc.dispose(); + return org.eclipse.jface.dialogs.Dialog.convertHorizontalDLUsToPixels(fontMetrics, IDialogConstants.BUTTON_WIDTH); + } + + public static Rectangle getPersistedWizardBounds(AppConfig appConfig) { + int xOffset = AppConfig.DEFAULT_WIZARD_X_OFFSET; + int yOffset = AppConfig.DEFAULT_WIZARD_Y_OFFSET; + int width = AppConfig.DEFAULT_WIZARD_WIDTH; + int height = AppConfig.DEFAULT_WIZARD_HEIGHT; + if (appConfig != null) { + try { + xOffset = appConfig.getInt(AppConfig.PROP_WIZARD_X_OFFSET); + yOffset = appConfig.getInt(AppConfig.PROP_WIZARD_Y_OFFSET); + width = appConfig.getInt(AppConfig.PROP_WIZARD_WIDTH); + height = appConfig.getInt(AppConfig.PROP_WIZARD_HEIGHT); + } catch (Exception ex) { + // no op + } + } + return new Rectangle(xOffset, yOffset, width, height); + } + + public static void setTableColWidth(Table table) { + if (table == null) { + return; + } + int numCols = table.getColumnCount(); + if (numCols == 0) { + return; + } + Rectangle currentClientAreaBounds = table.getClientArea(); + int desiredColWidth = currentClientAreaBounds.width / numCols; + if (desiredColWidth > 0) { + int currentTotalColWidth = 0; + for (int i=0; i < numCols; i++) { + currentTotalColWidth += table.getColumn(i).getWidth(); + } + if (currentTotalColWidth > currentClientAreaBounds.width) { + return; // do not change column width if current dialog width is less than total column width + } + + for (int i=0; i < numCols; i++) { + if (table.getColumn(i).getWidth() < desiredColWidth) { + table.getColumn(i).setWidth(desiredColWidth); + } + } + } + } } diff --git a/src/main/java/com/salesforce/dataloader/ui/URLUtil.java b/src/main/java/com/salesforce/dataloader/ui/URLUtil.java new file mode 100644 index 000000000..4f4debfbb --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/URLUtil.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import java.awt.Desktop; +import java.io.IOException; +import java.net.URI; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +public class URLUtil { + private static Logger logger = DLLogManager.getLogger(URLUtil.class); + public static void openURL(String url) { + if (Desktop.isDesktopSupported()) { + Desktop desktop = Desktop.getDesktop(); + try { + logger.debug("trying to use desktop.browse() method"); + desktop.browse(new URI(url)); + } catch (Exception e) { + logger.debug(e.getMessage()); + openURLUsingNativeCommand(url); + } + } else { + logger.debug("trying to use native command"); + openURLUsingNativeCommand(url); + } + } + + private static void openURLUsingNativeCommand(String url) { + Runtime runtime = Runtime.getRuntime(); + String osName = System.getProperty("os.name"); + try { + if (osName.toLowerCase().indexOf("mac") >= 0) { + logger.debug("trying to use open command on mac"); + runtime.exec("open " + url); + } + else if (osName.toLowerCase().indexOf("win") >= 0) { + logger.debug("trying to use rundll32 command on windows"); + runtime.exec("rundll32 url.dll,FileProtocolHandler " + url); + } else { //assume Unix or Linux + try { + logger.debug("trying to use xdg-open command on linux"); + runtime.exec("xdg-open " + url); + } catch (IOException e) { + logger.debug(e.getMessage()); + logger.debug("trying to browser-specific command on linux"); + String[] browsers = { + "firefox", "chrome", "opera", "konqueror", "epiphany", "mozilla", "netscape" }; + String browser = null; + for (int count = 0; count < browsers.length && browser == null; count++) + if (runtime.exec( + new String[] {"which", browsers[count]}).waitFor() == 0) { + browser = browsers[count]; + } + if (browser == null) { + throw new Exception("Could not find web browser"); + } else { + runtime.exec(new String[] {browser, url}); + } + } + } + } + catch (Exception e) { + logger.error(e.getMessage()); + } + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/UsernamePasswordLoginControl.java b/src/main/java/com/salesforce/dataloader/ui/UsernamePasswordLoginControl.java new file mode 100644 index 000000000..0e0337222 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/UsernamePasswordLoginControl.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.model.LoginCriteria; + +import java.util.ArrayList; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; + +/** + * LoginStandardControl is the way to login to the api + */ +public class UsernamePasswordLoginControl extends Composite { + + private final Button loginButton; + private final Text userName; + private final Text password; + private final AuthenticationRunner authRunner; + private final Label loginStatusLabel; + private final Text sessionId; + private final boolean authUsingSessionId; + private final LoginPage loginPage; + + public UsernamePasswordLoginControl(Composite parent, int style, LoginPage loginPage, AuthenticationRunner authRunner, boolean authUsingSessionId) { + super(parent, style); + this.authUsingSessionId = authUsingSessionId; + this.authRunner = authRunner; + this.loginPage = loginPage; + GridData data = new GridData(GridData.FILL_BOTH); + this.setLayoutData(data); + GridLayout layout = new GridLayout(2, true); + layout.verticalSpacing = 10; + this.setLayout(layout); + + Label usernameLabel = new Label(this, SWT.RIGHT); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + usernameLabel.setLayoutData(data); + usernameLabel.setText(Labels.getString("LoginPage.username")); + userName = new Text(this, SWT.LEFT | SWT.BORDER); + userName.setText(authRunner.getConfig().getString(AppConfig.PROP_USERNAME)); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); + userName.setLayoutData(data); + + userName.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent e){} + @Override + public void keyPressed(KeyEvent e){ + if (e.character == '\r') { + password.setFocus(); + } + } + }); + + Text pwdOrSessionIdLabel = new Text(this, SWT.RIGHT | SWT.WRAP | SWT.READ_ONLY); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + pwdOrSessionIdLabel.setLayoutData(data); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); + if (authUsingSessionId) { + pwdOrSessionIdLabel.setText(Labels.getString("LoginPage.sessionId")); + sessionId = new Text(this, SWT.LEFT | SWT.BORDER); + sessionId.setText(authRunner.getConfig().getString(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID)); + sessionId.setLayoutData(data); + password = null; + } else { + pwdOrSessionIdLabel.setText(Labels.getString("LoginPage.password")); + pwdOrSessionIdLabel.setToolTipText(Labels.getString("LoginPage.TooltipPassword")); + password = new Text(this, SWT.PASSWORD | SWT.LEFT | SWT.BORDER); + password.setText(""); + password.setToolTipText(Labels.getString("LoginPage.TooltipPassword")); + password.setLayoutData(data); + password.addKeyListener(new KeyListener() { + @Override + public void keyReleased(KeyEvent e){} + @Override + public void keyPressed(KeyEvent e) { + if (!"".equals(loginStatusLabel.getText())) { + loginStatusLabel.setText(""); // clear the login status text + } + if (e.character == '\r') { + attempt_login(); + } + } + }); + sessionId = null; + } + + Label envLabel = new Label(this, SWT.RIGHT | SWT.WRAP); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + envLabel.setLayoutData(data); + envLabel.setText(Labels.getString("LoginPage.environment")); + ArrayList environments = authRunner.getConfig().getStrings(AppConfig.PROP_SERVER_ENVIRONMENTS); + + Combo envDropdown = new Combo(this, SWT.DROP_DOWN | SWT.BORDER); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); + envDropdown.setLayoutData(data); + for (String label: environments) { + envDropdown.add(label); + } + String currentEnvironment = authRunner.getConfig().getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT); + if (environments.contains(currentEnvironment)) { + envDropdown.setText(currentEnvironment); + } + + @SuppressWarnings("unused") + Label emptyLabel = new Label(this, SWT.RIGHT); + emptyLabel.setText(""); + loginButton = new Button(this, SWT.PUSH | SWT.CENTER | SWT.FLAT); + loginButton.setText(Labels.getString("LoginPage.login")); + loginButton.addListener(SWT.Selection, this::loginButton_Clicked); + data = new GridData(GridData.HORIZONTAL_ALIGN_END); + int widthHint = UIUtils.getControlWidth(loginButton); + Point minSize = loginButton.computeSize(SWT.DEFAULT, SWT.DEFAULT, true); + data.widthHint = Math.max(widthHint, minSize.x); + loginButton.setLayoutData(data); + + loginStatusLabel = new Label(this, SWT.LEFT | SWT.WRAP); + loginStatusLabel.setText(""); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + GC gc = new GC(loginStatusLabel); + Point TEXT_SIZE = gc.textExtent("A"); + gc.dispose(); + data.heightHint = TEXT_SIZE.y * 3; + loginStatusLabel.setLayoutData(data); + } + + private void loginButton_Clicked(Event event) { + attempt_login(); + } + + private void attempt_login() { + LoginCriteria criteria = null; + if (authUsingSessionId) { + criteria = new LoginCriteria(LoginCriteria.SessionIdLogin); + criteria.setSessionId(sessionId.getText()); + } else { + criteria = new LoginCriteria(LoginCriteria.UsernamePasswordLogin); + criteria.setPassword(password.getText()); + } + criteria.setInstanceUrl(this.authRunner.getConfig().getAuthEndpointForCurrentEnv()); + criteria.setUserName(userName.getText()); + authRunner.login(criteria, this::setLoginStatus); + } + + private void setLoginStatus(String statusStr) { + if (this.loginPage.controller.isLoggedIn()) { + loginButton.setEnabled(false); + } + loginStatusLabel.setText(statusStr); + this.loginPage.setPageComplete(); + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/WelcomeScreenDialog.java b/src/main/java/com/salesforce/dataloader/ui/WelcomeScreenDialog.java new file mode 100644 index 000000000..bb28499b6 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/WelcomeScreenDialog.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui; + +import org.eclipse.jface.dialogs.IDialogConstants; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.*; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; + +/** + * Splash screen for the loader. + * + * @author Lexi Viripaeff + * @since 6.0 + */ +public class WelcomeScreenDialog extends LoaderTitleAreaDialog { + + private final AppConfig appConfig; + + /** + * MyTitleAreaDialog constructor + * + * @param shell + * the parent shell + */ + public WelcomeScreenDialog(Shell activeShell, AppConfig cfg) { + super(activeShell); + this.appConfig = cfg; + } + + /** + * Creates the dialog's contents + * + * @param parent + * the parent composite + * @return Control + */ + @Override + protected Control createContents(Composite parent) { + Control contents = super.createContents(parent); + + // Set the title + setTitle(Labels.getString("TitleDialog.title")); //$NON-NLS-1$ + + // Set the message + setMessage(Labels.getString("TitleDialog.messageLineOne") //$NON-NLS-1$ + + System.getProperty("line.separator") + Labels.getString("TitleDialog.messageLineTwo")); //$NON-NLS-1$ //$NON-NLS-2$ + + // Set the image + setTitleImage(UIUtils.getImageRegistry().get("splashscreens")); //$NON-NLS-1$ + + return contents; + } + + /** + * Creates the gray area + * + * @param parent + * the parent composite + * @return Control + */ + @Override + protected Control createDialogArea(Composite parent) { + Composite composite = (Composite)super.createDialogArea(parent); + + return composite; + } + + /** + * Creates the buttons for the button bar + * + * @param parent + * the parent composite + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + // create all the buttons, in order + for (OperationInfo info : OperationInfo.ALL_OPERATIONS_IN_ORDER) { + final Button butt = createButton(parent, info.getDialogIdx(), info.getLabel(), false); + butt.setEnabled(info.isOperationAllowed(this.appConfig)); + Image img = info.getUIHelper().getIconImage(); + butt.setImage(img); + GridData gd = (GridData)butt.getLayoutData(); + gd.grabExcessHorizontalSpace = true; + gd.widthHint += img.getImageData().width; + butt.setLayoutData(gd); + } + createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, true); + } + + /** + * Sets the button behavior + */ + @Override + protected void buttonPressed(int buttonID) { + setReturnCode(buttonID); + close(); + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/WizardDialog.java b/src/main/java/com/salesforce/dataloader/ui/WizardDialog.java new file mode 100644 index 000000000..fd5b84abe --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/WizardDialog.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.ui; + +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Shell; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; + +public abstract class WizardDialog extends BaseDialog { + + public WizardDialog(Shell parent, Controller controller) { + super(parent, controller); + } + + @Override + protected void createContents(Shell shell) { + // TODO Auto-generated method stub + + } + + protected void setShellBounds(Shell dialogShell) { + Rectangle shellBounds = dialogShell.getBounds(); + Rectangle persistedWizardBounds = UIUtils.getPersistedWizardBounds(getController().getAppConfig()); + shellBounds.x = persistedWizardBounds.x + AppConfig.DIALOG_X_OFFSET; + shellBounds.y = persistedWizardBounds.y + AppConfig.DIALOG_Y_OFFSET; + dialogShell.setBounds(shellBounds); + } + + protected Rectangle getPersistedDialogBounds() { + AppConfig appConfig = getController().getAppConfig(); + Rectangle wizardBounds = UIUtils.getPersistedWizardBounds(appConfig); + int xOffset = wizardBounds.x + AppConfig.DIALOG_X_OFFSET; + int yOffset = wizardBounds.y + AppConfig.DIALOG_Y_OFFSET; + int width = wizardBounds.width; + int height = wizardBounds.height; + if (appConfig != null) { + try { + xOffset = appConfig.getInt(AppConfig.PROP_WIZARD_X_OFFSET) + AppConfig.DIALOG_X_OFFSET; + yOffset = appConfig.getInt(AppConfig.PROP_WIZARD_Y_OFFSET) + AppConfig.DIALOG_Y_OFFSET; + width = appConfig.getInt(AppConfig.DIALOG_BOUNDS_PREFIX + getClass().getSimpleName() + AppConfig.DIALOG_WIDTH_SUFFIX); + height = appConfig.getInt(AppConfig.DIALOG_BOUNDS_PREFIX + getClass().getSimpleName() + AppConfig.DIALOG_HEIGHT_SUFFIX); + } catch (Exception ex) { + // no op + } + } + if (width == 0) { + width = wizardBounds.width; + } + if (height == 0) { + height = wizardBounds.height; + } + return new Rectangle(xOffset, yOffset, width, height); + } + +} diff --git a/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVContentProvider.java b/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVContentProvider.java index 0ffb2230b..4d2040956 100644 --- a/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVContentProvider.java +++ b/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVContentProvider.java @@ -44,9 +44,8 @@ public class CSVContentProvider implements IStructuredContentProvider { * @return Object[] */ @Override - @SuppressWarnings("unchecked") public Object[] getElements(Object arg0) { - List rowList = (List) arg0; + List rowList = (List) arg0; return rowList.toArray(new List[rowList.size()]); } diff --git a/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVLabelProvider.java b/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVLabelProvider.java index fb8e85701..a22454766 100644 --- a/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVLabelProvider.java +++ b/src/main/java/com/salesforce/dataloader/ui/csvviewer/CSVLabelProvider.java @@ -57,7 +57,7 @@ public Image getColumnImage(Object arg0, int arg1) { */ @Override public String getColumnText(Object arg0, int arg1) { - List elem = (List)arg0; + List elem = (List)arg0; if (elem.size() > arg1) { Object obj = elem.get(arg1); return obj != null ? obj.toString() : ""; diff --git a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityContentProvider.java b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityContentProvider.java index 46c40c6d5..c12f22d1c 100644 --- a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityContentProvider.java +++ b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityContentProvider.java @@ -46,7 +46,7 @@ public class EntityContentProvider implements IStructuredContentProvider { */ @Override public Object[] getElements(Object arg0) { - return ((HashMap)arg0).values().toArray(); + return ((HashMap)arg0).values().toArray(); } /** diff --git a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityFilter.java b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityFilter.java index b8dbd1d94..7d3c5ab96 100644 --- a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityFilter.java +++ b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityFilter.java @@ -28,6 +28,8 @@ import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Text; import com.sforce.soap.partner.DescribeGlobalSObjectResult; @@ -35,6 +37,17 @@ * This class filters the entity list */ public class EntityFilter extends ViewerFilter { + private Text searchText; + private Button filterAllCheckbox; + public EntityFilter(Text search) { + super(); + this.searchText = search; + this.filterAllCheckbox = null; + } + + public void setFilterButtion(Button filterButton) { + this.filterAllCheckbox = filterButton; + } /** * Returns whether the specified element passes this filter * @@ -49,18 +62,40 @@ public class EntityFilter extends ViewerFilter { @Override public boolean select(Viewer arg0, Object arg1, Object arg2) { - String entityName = ((DescribeGlobalSObjectResult)arg2).getName(); - /* - * Account Case Contact Event Lead Opportunity Pricebook2 Product2 Task User Custom Objects - */ - if (entityName.equals("Account") || entityName.equals("Case") || entityName.equals("Contact") - || entityName.equals("Event") || entityName.equals("Lead") || entityName.equals("Opportunity") - || entityName.equals("Pricebook2") || entityName.equals("Product2") || entityName.equals("Task") - || entityName.equals("User")) { - return true; - } else if (entityName.endsWith("__c")) { + DescribeGlobalSObjectResult describeSObjectResult = (DescribeGlobalSObjectResult)arg2; + String entityName = describeSObjectResult.getName(); + String entityLabel = describeSObjectResult.getLabel(); + boolean filterAllChecked = this.filterAllCheckbox.getSelection(); + if (filterAllChecked) { + return filterBySearchText(entityName, entityLabel); + } else { + /* + * Account Case Contact Event Lead Opportunity Pricebook2 Product2 Task User Custom Objects + */ + if (entityName.equals("Account") || entityName.equals("Case") || entityName.equals("Contact") + || entityName.equals("Event") || entityName.equals("Lead") || entityName.equals("Opportunity") + || entityName.equals("Pricebook2") || entityName.equals("Product2") || entityName.equals("Task") + || entityName.equals("User")) { + return filterBySearchText(entityName, entityLabel); + } else if (entityName.endsWith("__c")) { + return filterBySearchText(entityName, entityLabel); + } + return false; + } + } + + private boolean filterBySearchText(String entityName, String entityLabel) { + String searchText = this.searchText.getText(); + if (searchText != null && !searchText.isEmpty()) { + searchText = searchText.toLowerCase(); + if ((entityName != null && entityName.toLowerCase().contains(searchText)) + || (entityLabel != null && entityLabel.toLowerCase().contains(searchText))) { + return true; + } else { + return false; + } + } else { // no search text specified return true; } - return false; } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityViewerComparator.java b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityViewerComparator.java new file mode 100644 index 000000000..7a26ab236 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityViewerComparator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui.entitySelection; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; + +import com.sforce.soap.partner.DescribeGlobalSObjectResult; + +/** + * Sorts the Entity Lists + * + * @author Lexi Viripaeff + * @since 6.0 + */ +public class EntityViewerComparator extends ViewerComparator { + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + DescribeGlobalSObjectResult d1 = (DescribeGlobalSObjectResult)e1; + DescribeGlobalSObjectResult d2 = (DescribeGlobalSObjectResult)e2; + + // Determine which column and do the appropriate sort + return d1.getLabel().compareToIgnoreCase(d2.getLabel()); + } + + +} diff --git a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityViewerSorter.java b/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityViewerSorter.java deleted file mode 100644 index 864fc48a9..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/entitySelection/EntityViewerSorter.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui.entitySelection; - -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerSorter; - -import com.sforce.soap.partner.DescribeGlobalSObjectResult; - -/** - * Sorts the Entity Lists - * - * @author Lexi Viripaeff - * @since 6.0 - */ -public class EntityViewerSorter extends ViewerSorter { - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - DescribeGlobalSObjectResult d1 = (DescribeGlobalSObjectResult)e1; - DescribeGlobalSObjectResult d2 = (DescribeGlobalSObjectResult)e2; - - // Determine which column and do the appropriate sort - return collator.compare(d1.getLabel(), d2.getLabel()); - } - - -} diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtrFieldLabelProvider.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtrFieldLabelProvider.java index 8b3cd6596..1c9428f7e 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtrFieldLabelProvider.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtrFieldLabelProvider.java @@ -26,36 +26,22 @@ package com.salesforce.dataloader.ui.extraction; +import org.eclipse.jface.viewers.CellLabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; -import org.eclipse.jface.viewers.ITableLabelProvider; -import org.eclipse.swt.graphics.Image; +import org.eclipse.jface.viewers.ViewerCell; +import org.eclipse.swt.widgets.Control; import com.sforce.soap.partner.Field; /** * */ -public class ExtrFieldLabelProvider implements ITableLabelProvider { +public class ExtrFieldLabelProvider extends CellLabelProvider { public ExtrFieldLabelProvider() { } - @Override - public Image getColumnImage(Object arg0, int arg1) { - return null; - } - - - /** - * Gets the text for the specified column - * - * @return String - */ - @Override - public String getColumnText(Object arg0, int arg1) { - return ((Field) arg0).getName(); - } - + /** * Adds a listener * @@ -64,6 +50,8 @@ public String getColumnText(Object arg0, int arg1) { @Override public void addListener(ILabelProviderListener arg0) { // Throw it away + @SuppressWarnings("unused") + ILabelProviderListener listener = arg0; } /** @@ -93,4 +81,35 @@ public boolean isLabelProperty(Object arg0, String arg1) { public void removeListener(ILabelProviderListener arg0) { // Do nothing } + + @Override + public void update(ViewerCell cell) { + Field field = (Field)cell.getElement(); + String fieldTypeStr = getFieldTypeStr(field); + cell.setText(field.getName() + " (" + fieldTypeStr + ")"); + String tooltipStr = field.getInlineHelpText(); + if (tooltipStr == null) { + tooltipStr = fieldTypeStr; + } else { + tooltipStr = fieldTypeStr + "\n" + tooltipStr; + } + Control control = cell.getControl(); + control.getParent().setToolTipText(tooltipStr); + for (Control child : control.getParent().getChildren()) { + child.setToolTipText(tooltipStr); + } + } + + private String getFieldTypeStr(Field field) { + String fieldTypeStr = field.getType().toString(); + if (fieldTypeStr.startsWith("_")) { + fieldTypeStr = fieldTypeStr.substring(1); + } + if ("string".equalsIgnoreCase(fieldTypeStr) || "textarea".equalsIgnoreCase(fieldTypeStr)) { + fieldTypeStr = fieldTypeStr + + ", " + + field.getLength(); + } + return fieldTypeStr; + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionDialog.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionDialog.java deleted file mode 100644 index d3c310ea4..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionDialog.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui.extraction; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.BusyIndicator; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.*; - -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.ui.Labels; -import com.salesforce.dataloader.ui.UIUtils; -import com.sforce.ws.ConnectionException; - -public class ExtractionDataSelectionDialog extends Dialog { - private String message; - private boolean success; - private Controller controller; - private Button ok; - private Label label; - - /** - * InputDialog constructor - * - * @param parent - * the parent - */ - public ExtractionDataSelectionDialog(Shell parent, Controller controller) { - // Pass the default styles here - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - this.controller = controller; - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public ExtractionDataSelectionDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - setText(Labels.getString("ExtractionDataSelectionDialog.title")); //$NON-NLS-1$ - setMessage(Labels.getString("ExtractionDataSelectionDialog.verifyingEntity")); //$NON-NLS-1$ - } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; - } - - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public boolean open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - BusyIndicator.showWhile(display, new Thread() { - @Override - public void run() { - try { - controller.setFieldTypes(); - controller.setReferenceDescribes(); - success = true; - ok.setEnabled(true); - label.setText(Labels.getString("ExtractionDataSelectionDialog.success")); //$NON-NLS-1$ - label.getParent().pack(); - ; - } catch (ConnectionException ex) { - success = false; - ok.setEnabled(true); - label.setText(Labels.getString("ExtractionDataSelectionDialog.errorValidating")); //$NON-NLS-1$ - } - } - }); - - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the sucess - return success; - } - - /** - * Creates the dialog's contents - * - * @param shell - * the dialog window - */ - private void createContents(final Shell shell) { - - GridData data; - - GridLayout layout = new GridLayout(2, false); - layout.verticalSpacing = 10; - shell.setLayout(layout); - - label = new Label(shell, SWT.NONE); - label.setText(message); - data = new GridData(); - data.horizontalSpan = 2; - data.widthHint = 250; - label.setLayoutData(data); - - //the bottow separator - Label labelSeparatorBottom = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL); - data = new GridData(GridData.FILL_HORIZONTAL); - data.horizontalSpan = 2; - labelSeparatorBottom.setLayoutData(data); - - //ok cancel buttons - new Label(shell, SWT.NONE); - - ok = new Button(shell, SWT.PUSH | SWT.FLAT); - ok.setText(Labels.getString("UI.ok")); //$NON-NLS-1$ - ok.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - shell.close(); - } - }); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - data.widthHint = 75; - ok.setLayoutData(data); - ok.setEnabled(false); - - shell.setDefaultButton(ok); - } -} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionPage.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionPage.java index 004801fe0..b8a8ba156 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionDataSelectionPage.java @@ -32,24 +32,19 @@ import org.eclipse.jface.viewers.*; import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.BusyIndicator; import org.eclipse.swt.events.*; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataAccessObjectFactory; -import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.exception.MappingInitializationException; +import com.salesforce.dataloader.ui.EntitySelectionListViewerUtil; import com.salesforce.dataloader.ui.Labels; import com.salesforce.dataloader.ui.UIUtils; -import com.salesforce.dataloader.ui.entitySelection.*; import com.sforce.soap.partner.DescribeGlobalSObjectResult; -import com.sforce.ws.ConnectionException; /** * Describe your class here. @@ -57,28 +52,15 @@ * @author Lexi Viripaeff * @since 6.0 */ -public class ExtractionDataSelectionPage extends WizardPage { - - private final Controller controller; +public class ExtractionDataSelectionPage extends ExtractionPage { // These filter extensions are used to filter which files are displayed. - private final EntityFilter filter = new EntityFilter(); private ListViewer lv; private Text fileText; public Composite comp; - private boolean success; public ExtractionDataSelectionPage(Controller controller) { - super( Labels.getString("ExtractionDataSelectionPage.title"), - Labels.getString("ExtractionDataSelectionPage.titleMsg"), - UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - this.controller = controller; - - // Set the description - setDescription(Labels.getString("ExtractionDataSelectionPage.description")); //$NON-NLS-1$ - - setPageComplete(false); + super("ExtractionDataSelectionPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ } @Override @@ -93,53 +75,19 @@ public void createControl(Composite parent) { comp = new Composite(parent, SWT.NONE); comp.setLayout(gridLayout); - - Label label = new Label(comp, SWT.RIGHT); - label.setText(Labels.getString("ExtractionDataSelectionPage.selectSforce")); //$NON-NLS-1$ - GridData data = new GridData(); - label.setLayoutData(data); - - // Add a checkbox to toggle filter - Button filterAll = new Button(comp, SWT.CHECK); - filterAll.setText(Labels.getString("ExtractionDataSelectionPage.showAll")); //$NON-NLS-1$ - data = new GridData(); - filterAll.setLayoutData(data); - - lv = new ListViewer(comp, SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER); - lv.setContentProvider(new EntityContentProvider()); - lv.setLabelProvider(new EntityLabelProvider()); - lv.setInput(null); - data = new GridData(GridData.FILL, GridData.FILL, true, true); - data.heightHint = 140; - data.widthHint = 140; - lv.getControl().setLayoutData(data); - lv.addFilter(filter); - lv.setSorter(new EntityViewerSorter()); + lv = EntitySelectionListViewerUtil.getEntitySelectionListViewer(this.getClass(), comp, this.controller.getAppConfig()); lv.addSelectionChangedListener(new ISelectionChangedListener() { @Override public void selectionChanged(SelectionChangedEvent event) { - checkPageComplete(); + setPageComplete(); } }); - //if we're logged in, set the input - if (controller.isLoggedIn()) { - setInput(controller.getEntityDescribes()); - } - - filterAll.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - if (((Button)event.widget).getSelection()) - lv.removeFilter(filter); - else - lv.addFilter(filter); - } - }); + setupPage(); Label clearLabel = new Label(comp, SWT.NONE); - data = new GridData(GridData.VERTICAL_ALIGN_END); + GridData data = new GridData(GridData.VERTICAL_ALIGN_END); data.heightHint = 20; clearLabel.setLayoutData(data); @@ -160,13 +108,12 @@ public void widgetSelected(SelectionEvent event) { fileText = new Text(compChooser, SWT.BORDER); fileText.setText(Labels.getString("ExtractionDataSelectionPage.defaultFileName")); //$NON-NLS-1$ data = new GridData(GridData.FILL_HORIZONTAL); - data.widthHint = 350; fileText.setLayoutData(data); fileText.addModifyListener(new ModifyListener() { @Override public void modifyText(ModifyEvent e) { - checkPageComplete(); + setPageComplete(); } }); @@ -185,18 +132,26 @@ public void widgetSelected(SelectionEvent event) { if (filename != null && !"".equals(filename)) { //$NON-NLS-1$ //set the text, and see if the page is valid fileText.setText(filename); - checkPageComplete(); + setPageComplete(); } } }); setControl(comp); } + + protected boolean setupPagePostLogin() { + if (this.controller.isLoggedIn()) { + setInput(this.controller.getEntityDescribes()); + lv.refresh(); + } + return true; + } /** * Function to dynamically set the entity list */ - public void setInput(Map entityDescribes) { + private void setInput(Map entityDescribes) { Map inputDescribes = new HashMap(); if (entityDescribes != null) { @@ -208,9 +163,8 @@ public void setInput(Map entityDescribes) { } } lv.setInput(inputDescribes); - lv.refresh(); lv.getControl().getParent().pack(); - + lv.refresh(); } private boolean checkEntityStatus() { @@ -221,7 +175,7 @@ private boolean checkEntityStatus() { } - private void checkPageComplete() { + public void setPageComplete() { if (!(fileText.getText().equals("")) && checkEntityStatus()) { //$NON-NLS-1$ setPageComplete(true); @@ -231,14 +185,6 @@ private void checkPageComplete() { } - /** - * Need to subclass this function to prevent the getNextPage() function being called before the button is clicked. - */ - @Override - public boolean canFlipToNextPage() { - return isPageComplete(); - } - /** * Returns the next page, describes SObject and performs the total size calculation * @@ -256,58 +202,28 @@ public IWizardPage getNextPage() { } } - Config config = controller.getConfig(); //get entity IStructuredSelection selection = (IStructuredSelection)lv.getSelection(); DescribeGlobalSObjectResult entity = (DescribeGlobalSObjectResult)selection.getFirstElement(); - config.setValue(Config.ENTITY, entity.getName()); - // set DAO - CSV file name - config.setValue(Config.DAO_NAME, fileText.getText()); - // set DAO type to CSV - config.setValue(Config.DAO_TYPE, DataAccessObjectFactory.CSV_WRITE_TYPE); - controller.saveConfig(); try { - // create data access object for the extraction output - controller.createDao(); // reinitialize the data mapping (UI extraction currently uses only implicit mapping) - config.setValue(Config.MAPPING_FILE, ""); - controller.createMapper(); - } catch (DataAccessObjectInitializationException e) { - MessageBox msgBox = new MessageBox(getShell(), SWT.OK | SWT.ICON_ERROR); - msgBox.setMessage(Labels.getString("ExtractionDataSelectionPage.extractOutputError")); //$NON-NLS-1$ - msgBox.open(); - return this; + controller.initializeOperation(DataAccessObjectFactory.CSV_WRITE_TYPE, + fileText.getText(), entity.getName()); } catch (MappingInitializationException e) { UIUtils.errorMessageBox(getShell(), e); return this; } - BusyIndicator.showWhile(Display.getDefault(), new Thread() { - @Override - public void run() { - try { - controller.setFieldTypes(); - controller.setReferenceDescribes(); - success = true; - } catch (ConnectionException e) { - success = false; - } - } - }); - - if (success) { - //set the query - ExtractionSOQLPage soql = (ExtractionSOQLPage)getWizard().getPage("SOQL"); //$NON-NLS-1$ - soql.initializeSOQLText(); - soql.setPageComplete(true); + //set the query + ExtractionSOQLPage soql = (ExtractionSOQLPage)getWizard().getPage(ExtractionSOQLPage.class.getSimpleName()); //$NON-NLS-1$ + soql.setupPage(); + soql.setPageComplete(true); + return super.getNextPage(); + } - return super.getNextPage(); - } else { - MessageBox msgBox = new MessageBox(getShell(), SWT.OK | SWT.ICON_ERROR); - msgBox.setMessage(Labels.getString("ExtractionDataSelectionPage.initError")); //$NON-NLS-1$ - msgBox.open(); - return this; - } + // nothing to finish before moving to the next page + public boolean finishPage() { + return true; } } diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishDialog.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishDialog.java index 9b4026ae0..c6ae1b006 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishDialog.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishDialog.java @@ -32,15 +32,13 @@ import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.ui.*; -public class ExtractionFinishDialog extends Dialog { - private String message; +public class ExtractionFinishDialog extends WizardDialog { private Label label; - private Controller controller; private Button ok; /** @@ -50,65 +48,7 @@ public class ExtractionFinishDialog extends Dialog { * the parent */ public ExtractionFinishDialog(Shell parent, Controller controller) { - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - this.controller = controller; - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public ExtractionFinishDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - setText(Labels.getString("ExtractionFinishDialog.title")); //$NON-NLS-1$ - } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; - } - - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public boolean open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the sucess - return true; + super(parent, controller); } /** @@ -117,7 +57,7 @@ public boolean open() { * @param shell * the dialog window */ - private void createContents(final Shell shell) { + protected void createContents(final Shell shell) { GridData data; @@ -132,7 +72,7 @@ private void createContents(final Shell shell) { labelInfo.setLayoutData(data); label = new Label(shell, SWT.NONE); - label.setText(message); + label.setText(getMessage()); data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.VERTICAL_ALIGN_BEGINNING); label.setLayoutData(data); @@ -149,7 +89,7 @@ private void createContents(final Shell shell) { buttonComp.setLayoutData(data); // error status output is optional - boolean enableStatusOutput = controller.getConfig().getBoolean(Config.ENABLE_EXTRACT_STATUS_OUTPUT); + boolean enableStatusOutput = getController().getAppConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT); if (enableStatusOutput) { layout = new GridLayout(3, false); } else { @@ -164,7 +104,7 @@ private void createContents(final Shell shell) { viewExtraction.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - openViewer(controller.getConfig().getString(Config.DAO_NAME)); + openViewer(getController().getAppConfig().getString(AppConfig.PROP_DAO_NAME), false); } @Override @@ -181,7 +121,7 @@ public void widgetDefaultSelected(SelectionEvent e) {} viewErrors.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - openViewer(controller.getConfig().getString(Config.OUTPUT_ERROR)); + openViewer(getController().getAppConfig().getString(AppConfig.PROP_OUTPUT_ERROR), true); } @Override @@ -205,9 +145,8 @@ public void widgetSelected(SelectionEvent event) { shell.setDefaultButton(ok); } - private void openViewer(String filename) { - CSVViewerDialog dlg = new CSVViewerDialog(getParent(), controller); - dlg.setUseCustomSplitter(false); + private void openViewer(String filename, boolean ignoreDelimiterConfig) { + CSVViewerDialog dlg = new CSVViewerDialog(getParent(), getController(), ignoreDelimiterConfig, true); dlg.setNumberOfRows(200000); dlg.setFileName(filename); try { diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishPage.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishPage.java index 13abbbe66..f8af49f4b 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionFinishPage.java @@ -28,88 +28,32 @@ import java.io.File; -import org.eclipse.jface.preference.DirectoryFieldEditor; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; - -import com.salesforce.dataloader.config.Config; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.ProcessInitializationException; +import com.salesforce.dataloader.ui.FinishPage; import com.salesforce.dataloader.ui.Labels; import com.salesforce.dataloader.ui.UIUtils; /** - * Last page of extraction wizard which lets the user select directory for status files (currently, only error status is + * Last page of extraction wizard which lets the user select folder for status files (currently, only error status is * generated in case of extract) * * @author Alex Warshavsky * @since 8.0 */ -public class ExtractionFinishPage extends ExtractionPage { - - private final Controller controller; - private DirectoryFieldEditor dirFE; +public class ExtractionFinishPage extends FinishPage { public ExtractionFinishPage(Controller controller) { - super(Labels.getString("FinishPage.title"), Labels.getString("FinishPage.finishMsg"), UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - this.controller = controller; - setPageComplete(false); - - // Set the description - setDescription(Labels.getString("FinishPage.selectDir")); //$NON-NLS-1$ - + super("ExtractionFinishPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ } @Override - public void createControl(Composite parent) { - Composite comp = new Composite(parent, SWT.NONE); - //comp.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); - - GridLayout gridLayout = new GridLayout(1, false); - gridLayout.horizontalSpacing = 10; - gridLayout.marginHeight = 20; - comp.setLayout(gridLayout); - - Label label = new Label(comp, SWT.CENTER); - label.setText(Labels.getString("FinishPage.overwritten")); //$NON-NLS-1$ - - Composite dirComp = new Composite(comp, SWT.NONE); - GridData data = new GridData(); - data.widthHint = 400; - dirComp.setLayoutData(data); - - dirFE = new DirectoryFieldEditor( - Labels.getString("FinishPage.output"), Labels.getString("FinishPage.chooseDir"), dirComp); //$NON-NLS-1$ //$NON-NLS-2$ - dirFE.setStringValue(controller.getConfig().getString(Config.OUTPUT_STATUS_DIR)); - - setControl(comp); - } - - public String getOutputDir() { - return dirFE.getStringValue(); - } - - @Override - public boolean canFlipToNextPage() { - // this is always the last page, disable next - return false; - } - - /* - * (non-Javadoc) - * @see com.salesforce.dataloader.ui.extraction.ExtractionPage#finishPage() - */ - @Override - public boolean finishPage() { + public boolean finishAllowed() { // validate the status output String outputDirName = getOutputDir(); File statusDir = new File(outputDirName); if (!statusDir.exists() || !statusDir.isDirectory()) { - UIUtils.errorMessageBox(getShell(), Labels.getString("LoadWizard.errorValidDirectory")); //$NON-NLS-1$ + UIUtils.errorMessageBox(getShell(), Labels.getString("LoadWizard.errorValidFolder")); //$NON-NLS-1$ return false; } // set the files for status output @@ -122,4 +66,14 @@ public boolean finishPage() { } return true; } + + protected boolean setupPagePostLogin() { + setPageComplete(); + return true; + } + + @Override + public String getConfigInfo() { + return ExtractionPage.getConfigInfo(controller.getAppConfig()); + } } diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionInputDialog.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionInputDialog.java deleted file mode 100644 index e59d26c78..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionInputDialog.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui.extraction; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.*; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.FontData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.*; - -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.ui.Labels; -import com.salesforce.dataloader.ui.UIUtils; -import com.sforce.soap.partner.Connector; - -public class ExtractionInputDialog extends Dialog { - private String message; - private String input; - private Controller controller; - private Text textBatch; - private Text textEndpoint; - private Text textTimeout; - private Button buttonCompression; - private Text textProxyHost; - private Text textProxyPort; - private Text textProxyUsername; - private Text textProxyPassword; - - /** - * InputDialog constructor - * - * @param parent - * the parent - */ - public ExtractionInputDialog(Shell parent, Controller controller) { - // Pass the default styles here - this(parent, SWT.DIALOG_TRIM | SWT.APPLICATION_MODAL | SWT.RESIZE); - - this.controller = controller; - } - - /** - * InputDialog constructor - * - * @param parent - * the parent - * @param style - * the style - */ - public ExtractionInputDialog(Shell parent, int style) { - // Let users override the default styles - super(parent, style); - setText(Labels.getString("ExtractionInputDialog.title")); //$NON-NLS-1$ - setMessage(Labels.getString("ExtractionInputDialog.message")); //$NON-NLS-1$ - } - - /** - * Gets the message - * - * @return String - */ - public String getMessage() { - return message; - } - - /** - * Sets the message - * - * @param message - * the new message - */ - public void setMessage(String message) { - this.message = message; - } - - /** - * Gets the input - * - * @return String - */ - public String getInput() { - return input; - } - - /** - * Sets the input - * - * @param input - * the new input - */ - public void setInput(String input) { - this.input = input; - } - - /** - * Opens the dialog and returns the input - * - * @return String - */ - public String open() { - // Create the dialog window - Shell shell = new Shell(getParent(), getStyle()); - shell.setText(getText()); - shell.setImage(UIUtils.getImageRegistry().get("sfdc_icon")); //$NON-NLS-1$ - createContents(shell); - shell.pack(); - shell.open(); - Display display = getParent().getDisplay(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) { - display.sleep(); - } - } - // Return the entered value, or null - return input; - } - - /** - * Creates the dialog's contents - * - * @param shell - * the dialog window - */ - private void createContents(final Shell shell) { - - Config config = controller.getConfig(); - - GridData data; - GridLayout layout = new GridLayout(1, false); - layout.verticalSpacing = 10; - layout.marginHeight = 0; - layout.marginWidth = 0; - shell.setLayout(layout); - - data = new GridData(GridData.FILL_HORIZONTAL); - data.heightHint = 50; - data.widthHint = 400; - - Composite topComp = new Composite(shell, SWT.NONE); - layout = new GridLayout(1, false); - layout.marginHeight = 0; - layout.marginWidth = 0; - layout.verticalSpacing = 0; - topComp.setLayout(layout); - topComp.setLayoutData(data); - - Label blank = new Label(topComp, SWT.NONE); - data = new GridData(GridData.FILL_HORIZONTAL); - data.heightHint = 10; - blank.setLayoutData(data); - blank.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); - - // Show the message - Label label = new Label(topComp, SWT.NONE); - label.setText(message); - data = new GridData(GridData.FILL_HORIZONTAL); - data.heightHint = 30; - data.widthHint = 370; - - Font f = label.getFont(); - FontData[] farr = f.getFontData(); - FontData fd = farr[0]; - fd.setStyle(SWT.BOLD); - label.setFont(new Font(Display.getCurrent(), fd)); - - label.setLayoutData(data); - label.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); - - Label labelSeparator = new Label(topComp, SWT.SEPARATOR | SWT.HORIZONTAL); - data = new GridData(GridData.FILL_HORIZONTAL); - labelSeparator.setLayoutData(data); - - Composite restComp = new Composite(shell, SWT.NONE); - data = new GridData(GridData.FILL_BOTH); - restComp.setLayoutData(data); - layout = new GridLayout(2, false); - layout.verticalSpacing = 10; - restComp.setLayout(layout); - - //extraction batch size - Label labelBatch = new Label(restComp, SWT.RIGHT); - labelBatch.setText(Labels.getString("ExtractionInputDialog.querySize")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelBatch.setLayoutData(data); - - textBatch = new Text(restComp, SWT.BORDER); - textBatch.setText(config.getString(Config.EXTRACT_REQUEST_SIZE)); - textBatch.setTextLimit(4); - textBatch.addVerifyListener(new VerifyListener() { - @Override - public void verifyText(VerifyEvent event) { - event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); - } - }); - - //endpoint - Label labelEndpoint = new Label(restComp, SWT.RIGHT); - labelEndpoint.setText(Labels.getString("ExtractionInputDialog.serverURL")); //$NON-NLS-1$ - - textEndpoint = new Text(restComp, SWT.BORDER); - data = new GridData(); - data.widthHint = 250; - textEndpoint.setLayoutData(data); - String endpoint = config.getString(Config.ENDPOINT); - if ("".equals(endpoint)) { //$NON-NLS-1$ - endpoint = Connector.END_POINT; - } - textEndpoint.setText(endpoint); - - Label labelCompression = new Label(restComp, SWT.RIGHT); - labelCompression.setText(Labels.getString("ExtractionInputDialog.compression")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelCompression.setLayoutData(data); - buttonCompression = new Button(restComp, SWT.CHECK); - buttonCompression.setSelection(config.getBoolean(Config.NO_COMPRESSION)); - - //timeout size - Label labelTimeout = new Label(restComp, SWT.RIGHT); - labelTimeout.setText(Labels.getString("ExtractionInputDialog.timeout")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelTimeout.setLayoutData(data); - - textTimeout = new Text(restComp, SWT.BORDER); - textTimeout.setTextLimit(4); - textTimeout.setText(config.getString(Config.TIMEOUT_SECS)); - textTimeout.addVerifyListener(new VerifyListener() { - @Override - public void verifyText(VerifyEvent event) { - event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); - } - }); - - //proxy Host - Label labelProxyHost = new Label(restComp, SWT.RIGHT); - labelProxyHost.setText(Labels.getString("ExtractionInputDialog.proxyHost")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyHost.setLayoutData(data); - - textProxyHost = new Text(restComp, SWT.BORDER); - textProxyHost.setText(config.getString(Config.PROXY_HOST)); - data = new GridData(); - data.widthHint = 250; - textProxyHost.setLayoutData(data); - - //Proxy Port - Label labelProxyPort = new Label(restComp, SWT.RIGHT); - labelProxyPort.setText(Labels.getString("ExtractionInputDialog.proxyPort")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyPort.setLayoutData(data); - - textProxyPort = new Text(restComp, SWT.BORDER); - textProxyPort.setText(config.getString(Config.PROXY_PORT)); - textProxyPort.setTextLimit(4); - textProxyPort.addVerifyListener(new VerifyListener() { - @Override - public void verifyText(VerifyEvent event) { - event.doit = Character.isISOControl(event.character) || Character.isDigit(event.character); - } - }); - data = new GridData(); - data.widthHint = 25; - textProxyPort.setLayoutData(data); - - //Proxy Username - Label labelProxyUsername = new Label(restComp, SWT.RIGHT); - labelProxyUsername.setText(Labels.getString("ExtractionInputDialog.proxyUsername")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyUsername.setLayoutData(data); - - textProxyUsername = new Text(restComp, SWT.BORDER); - textProxyUsername.setText(config.getString(Config.PROXY_USERNAME)); - data = new GridData(); - data.widthHint = 120; - textProxyUsername.setLayoutData(data); - - //Proxy Password - Label labelProxyPassword = new Label(restComp, SWT.RIGHT); - labelProxyPassword.setText(Labels.getString("ExtractionInputDialog.proxyPassword")); //$NON-NLS-1$ - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - labelProxyPassword.setLayoutData(data); - - textProxyPassword = new Text(restComp, SWT.BORDER | SWT.PASSWORD); - textProxyPassword.setText(config.getString(Config.PROXY_PASSWORD)); - data = new GridData(); - data.widthHint = 120; - textProxyPassword.setLayoutData(data); - - Label blankAgain = new Label(restComp, SWT.NONE); - data = new GridData(); - data.horizontalSpan = 2; - blankAgain.setLayoutData(data); - - //the bottow separator - Label labelSeparatorBottom = new Label(restComp, SWT.SEPARATOR | SWT.HORIZONTAL); - data = new GridData(GridData.FILL_HORIZONTAL); - data.horizontalSpan = 2; - labelSeparatorBottom.setLayoutData(data); - - //ok cancel buttons - new Label(restComp, SWT.NONE); - - Composite buttonComp = new Composite(restComp, SWT.NONE); - data = new GridData(GridData.HORIZONTAL_ALIGN_END); - buttonComp.setLayoutData(data); - buttonComp.setLayout(new GridLayout(2, false)); - - // Create the OK button and add a handler - // so that pressing it will set input - // to the entered value - Button ok = new Button(buttonComp, SWT.PUSH | SWT.FLAT); - ok.setText(Labels.getString("UI.ok")); //$NON-NLS-1$ - ok.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - Config config = controller.getConfig(); - - //set the configValues - config.setValue(Config.EXTRACT_REQUEST_SIZE, textBatch.getText()); - config.setValue(Config.ENDPOINT, textEndpoint.getText()); - config.setValue(Config.TIMEOUT_SECS, textTimeout.getText()); - config.setValue(Config.NO_COMPRESSION, buttonCompression.getSelection()); - config.setValue(Config.PROXY_HOST, textProxyHost.getText()); - config.setValue(Config.PROXY_PASSWORD, textProxyPassword.getText()); - config.setValue(Config.PROXY_PORT, textProxyPort.getText()); - config.setValue(Config.PROXY_USERNAME, textProxyUsername.getText()); - - controller.saveConfig(); - - input = "OK"; //$NON-NLS-1$ - shell.close(); - } - }); - data = new GridData(); - data.widthHint = 75; - ok.setLayoutData(data); - - // Create the cancel button and add a handler - // so that pressing it will set input to null - Button cancel = new Button(buttonComp, SWT.PUSH | SWT.FLAT); - cancel.setText(Labels.getString("UI.cancel")); //$NON-NLS-1$ - cancel.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent event) { - input = null; - shell.close(); - } - }); - - data = new GridData(); - data.widthHint = 75; - cancel.setLayoutData(data); - - // Set the OK button as the default, so - // user can type input and press Enter - // to dismiss - shell.setDefaultButton(ok); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionPage.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionPage.java index c35bc65b8..10a12da77 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionPage.java @@ -25,8 +25,10 @@ */ package com.salesforce.dataloader.ui.extraction; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.wizard.WizardPage; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.ui.Labels; +import com.salesforce.dataloader.ui.OperationPage; /** * Class for the common code in the extraction wizard pages @@ -34,19 +36,25 @@ * @author Alex Warshavsky * @since 8.0 */ -public abstract class ExtractionPage extends WizardPage { +public abstract class ExtractionPage extends OperationPage { + + protected ExtractionPage(String name, Controller controller) { + super(name, controller); - /** - * @param pageName - * @param title - * @param titleImage - */ - protected ExtractionPage(String pageName, String title, ImageDescriptor titleImage) { - super(pageName, title, titleImage); } - /** - * finishing step for this page - */ - public abstract boolean finishPage(); + @Override + protected String getConfigInfo() { + return getConfigInfo(controller.getAppConfig()); + } + + public static String getConfigInfo(AppConfig appConfig) { + if (appConfig.isBulkAPIEnabled() || appConfig.isBulkV2APIEnabled()) { + return ""; + } + // Batch size settings are applicable only for SOAP API + return Labels.getString("ExtractionInputDialog.exportBatchSize") + + " " + + appConfig.getString(AppConfig.PROP_EXPORT_BATCH_SIZE); + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSOQLPage.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSOQLPage.java index 025f13dc0..d93ae9ac0 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSOQLPage.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSOQLPage.java @@ -29,20 +29,19 @@ import java.util.*; import java.util.List; -import org.apache.log4j.Logger; import org.eclipse.jface.viewers.*; import org.eclipse.jface.wizard.IWizardPage; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.CCombo; import org.eclipse.swt.events.*; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.*; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.ui.Labels; -import com.salesforce.dataloader.ui.UIUtils; import com.sforce.soap.partner.*; /** @@ -53,10 +52,8 @@ */ public class ExtractionSOQLPage extends ExtractionPage { - private final Controller controller; - private final Logger logger = Logger.getLogger(ExtractionSOQLPage.class); private Text soqlText; - private Field[] fields; + private Field[] fieldsInSObject; private CheckboxTableViewer fieldViewer; private final String[] operationsDisplayNormal = { "equals", "not equals", "less than", "greater than", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ "less than or equals", "greater than or equals" }; //$NON-NLS-1$ //$NON-NLS-2$ @@ -65,7 +62,7 @@ public class ExtractionSOQLPage extends ExtractionPage { "less than or equals", "greater than or equals" }; //$NON-NLS-1$ //$NON-NLS-2$ private final String[] operationsDisplayMulti = { "equals", "not equals", "includes", "excludes" }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ private HashMap operationMap; - private Combo fieldCombo; + private CCombo whereFieldCombo; private Composite whereComp; private Composite builderComp; private boolean isPickListField; @@ -86,26 +83,20 @@ public class ExtractionSOQLPage extends ExtractionPage { private static final int FIELD_NORMAL = 2; // SOQL building variables - private StringBuffer fromPart; - private final String SELECT = "Select "; //$NON-NLS-1$ - private StringBuffer fieldPart = new StringBuffer(); + private StringBuffer fromEntityPart; + private final String SELECT = "SELECT "; //$NON-NLS-1$ + private StringBuffer selectFieldsClausePart = new StringBuffer(); private StringBuffer wherePart = new StringBuffer(); - private Combo operCombo; + private CCombo operCombo; + private ArrayList selectedFieldsInFieldViewer = new ArrayList(); + private Button addWhereClause; + private Button clearAllWhereClauses; public ExtractionSOQLPage(Controller controller) { - super( - Labels.getString("ExtractionSOQLPage.title"), Labels.getString("ExtractionSOQLPage.titleMessage"), UIUtils.getImageRegistry().getDescriptor("splashscreens")); //$NON-NLS-1$ //$NON-NLS-2$ - - this.controller = controller; - - // Set the description - setDescription(Labels.getString("ExtractionSOQLPage.description")); //$NON-NLS-1$ + super("ExtractionSOQLPage", controller); //$NON-NLS-1$ //$NON-NLS-2$ initOperMap(); - - setPageComplete(false); lastFieldType = FIELD_NORMAL; isPickListField = false; - } private void initOperMap() { @@ -140,9 +131,7 @@ public void createControl(Composite parent) { comp.setLayout(gridLayout); builderComp = new Composite(comp, SWT.NONE); - data = new GridData(GridData.FILL_BOTH); - data.heightHint = 170; - data.widthHint = 650; + data = new GridData(SWT.FILL, SWT.FILL, true, true); builderComp.setLayoutData(data); gridLayout = new GridLayout(2, false); gridLayout.horizontalSpacing = 25; @@ -154,39 +143,75 @@ public void createControl(Composite parent) { Label fieldWhere = new Label(builderComp, SWT.LEFT); fieldWhere.setText(Labels.getString("ExtractionSOQLPage.createClauses")); //$NON-NLS-1$ - fieldViewer = CheckboxTableViewer.newCheckList(builderComp, SWT.BORDER); + Composite fieldComp = new Composite(builderComp, SWT.NONE); + gridLayout = new GridLayout(1, false); + gridLayout.horizontalSpacing = 25; + fieldComp.setLayout(gridLayout); + data = new GridData(GridData.FILL_BOTH); + fieldComp.setLayoutData(data); + + Text search = new Text(fieldComp, SWT.SEARCH | SWT.ICON_CANCEL | SWT.ICON_SEARCH); + data = new GridData(GridData.FILL_HORIZONTAL); + search.setLayoutData(data); + + fieldViewer = CheckboxTableViewer.newCheckList(fieldComp, SWT.BORDER); + ColumnViewerToolTipSupport.enableFor(fieldViewer); fieldViewer.setLabelProvider(new ExtrFieldLabelProvider()); fieldViewer.setContentProvider(new ExtrFieldContentProvider()); - data = new GridData(GridData.FILL_VERTICAL); - data.widthHint = 135; + data = new GridData(GridData.FILL_BOTH); + data.widthHint = 50; + data.heightHint = 120; fieldViewer.getTable().setLayoutData(data); + fieldViewer.getTable().getHorizontalBar().setVisible(true); + + FieldFilter filter = new FieldFilter(search); + fieldViewer.addFilter(filter); fieldViewer.addCheckStateListener(new ICheckStateListener() { @Override public void checkStateChanged(CheckStateChangedEvent event) { - generateFieldPart(); - generateSOQLText(); + Field field = (Field)event.getElement(); + if (event.getChecked()) { + selectedFieldsInFieldViewer.add(field); + } else { + for (Field selectedField : selectedFieldsInFieldViewer) { + if (selectedField.getName().equalsIgnoreCase(field.getName())) { + selectedFieldsInFieldViewer.remove(field); + break; + } + } + } + updateSoQLTextAndButtons(); + } + }); + + search.addSelectionListener(new SelectionAdapter() { + public void widgetDefaultSelected(SelectionEvent e) { + preserveFieldViewerCheckedItems(); + fieldViewer.refresh(); + } + }); + + search.addListener(SWT.KeyUp, new Listener() { + public void handleEvent(Event e) { + preserveFieldViewerCheckedItems(); + fieldViewer.refresh(); } }); whereComp = new Composite(builderComp, SWT.NONE); - data = new GridData(GridData.FILL_BOTH); + data = new GridData(GridData.FILL_VERTICAL); whereComp.setLayoutData(data); - gridLayout = new GridLayout(3, false); + gridLayout = new GridLayout(2, false); whereComp.setLayout(gridLayout); - Label fLabel = new Label(whereComp, SWT.LEFT); + Label fLabel = new Label(whereComp, SWT.RIGHT); fLabel.setText(Labels.getString("ExtractionSOQLPage.fields")); //$NON-NLS-1$ + fLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); - Label opLabel = new Label(whereComp, SWT.CENTER); - opLabel.setText(Labels.getString("ExtractionSOQLPage.operation")); //$NON-NLS-1$ - - Label valLabel = new Label(whereComp, SWT.CENTER); - valLabel.setText(Labels.getString("ExtractionSOQLPage.value")); //$NON-NLS-1$ - - fieldCombo = new Combo(whereComp, SWT.DROP_DOWN); - operCombo = new Combo(whereComp, SWT.DROP_DOWN); - operCombo.setItems(operationsDisplayNormal); - fieldCombo.addSelectionListener(new SelectionListener() { + whereFieldCombo = new CCombo(whereComp, SWT.DROP_DOWN | SWT.LEFT); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); + whereFieldCombo.setLayoutData(data); + whereFieldCombo.addSelectionListener(new SelectionListener() { @Override public void widgetDefaultSelected(SelectionEvent event) {} @@ -194,12 +219,13 @@ public void widgetDefaultSelected(SelectionEvent event) {} @Override public void widgetSelected(SelectionEvent event) { // get the selected string - String name = fieldCombo.getText(); + String name = whereFieldCombo.getText(); + setAddWhereButtonState(); // get the corresponding field if (name != null && name.length() > 0) { - for (int i = 0; i < fields.length; i++) { - Field field = fields[i]; + for (int i = 0; i < fieldsInSObject.length; i++) { + Field field = fieldsInSObject[i]; if (name.equals(field.getName())) { // picklist values @@ -213,13 +239,16 @@ public void widgetSelected(SelectionEvent event) { // operations values if (field.getType() == FieldType.string && lastFieldType != FIELD_STRING) { operCombo.setItems(operationsDisplayString); + operCombo.setText(operationsDisplayString[0]); setLastFieldType(FIELD_STRING); } else if (field.getType() == FieldType.multipicklist && lastFieldType != FIELD_MULTI) { operCombo.setItems(operationsDisplayMulti); + operCombo.setText(operationsDisplayMulti[0]); setLastFieldType(FIELD_MULTI); } else if (lastFieldType != FIELD_NORMAL && field.getType() != FieldType.multipicklist && !field.getType().toString().equals("string")) { operCombo.setItems(operationsDisplayNormal); + operCombo.setText(operationsDisplayNormal[0]); setLastFieldType(FIELD_NORMAL); } break; @@ -228,11 +257,71 @@ public void widgetSelected(SelectionEvent event) { } } }); + + whereFieldCombo.addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + // Do nothing + } + }); + + whereFieldCombo.addMouseListener(new MouseListener() { + @Override + public void mouseDoubleClick(MouseEvent arg0) { + // Do nothing + + } + + @Override + public void mouseDown(MouseEvent arg0) { + String text = whereFieldCombo.getText(); + updateWhereFieldComboList(text); + } + + @Override + public void mouseUp(MouseEvent arg0) { + // setAddWhereButtonState(); + } + }); + + Label opLabel = new Label(whereComp, SWT.RIGHT); + opLabel.setText(Labels.getString("ExtractionSOQLPage.operation")); //$NON-NLS-1$ + opLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); + operCombo = new CCombo(whereComp, SWT.DROP_DOWN | SWT.LEFT | SWT.READ_ONLY); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); + operCombo.setLayoutData(data); + operCombo.setItems(operationsDisplayNormal); + operCombo.setText(operationsDisplayNormal[0]); + operCombo.addSelectionListener(new SelectionListener() { + + @Override + public void widgetDefaultSelected(SelectionEvent event) {} + + @Override + public void widgetSelected(SelectionEvent event) { + setAddWhereButtonState(); + } + }); + + Label valLabel = new Label(whereComp, SWT.RIGHT); + valLabel.setText(Labels.getString("ExtractionSOQLPage.value")); //$NON-NLS-1$ + valLabel.setLayoutData(new GridData(GridData.HORIZONTAL_ALIGN_END)); valueText = new Text(whereComp, SWT.BORDER); - data = new GridData(); - data.widthHint = 85; + valueText.setTextLimit(70); + data = new GridData(GridData.HORIZONTAL_ALIGN_BEGINNING | GridData.FILL_HORIZONTAL); valueText.setLayoutData(data); + valueText.addKeyListener(new KeyListener() { + public void keyReleased(KeyEvent key) { + setAddWhereButtonState(); + } + + @Override + public void keyPressed(KeyEvent arg0) { + // DO NOTHING + + } + }); valueText.addFocusListener(new FocusListener() { @Override public void focusGained(FocusEvent e) { @@ -271,31 +360,37 @@ public void focusGained(FocusEvent e) { } else { isFocusDialogWanted = true; } - } @Override - public void focusLost(FocusEvent e) {} + public void focusLost(FocusEvent e) { + // Do Nothing + } }); - Button addWhere = new Button(whereComp, SWT.PUSH | SWT.FLAT); - addWhere.setText(Labels.getString("ExtractionSOQLPage.addCondition")); //$NON-NLS-1$ - addWhere.addSelectionListener(new SelectionListener() { + Composite whereButtonsComp = new Composite(whereComp, SWT.NONE); + data = new GridData(GridData.FILL_BOTH); + data.horizontalSpan = 2; + whereButtonsComp.setLayoutData(data); + gridLayout = new GridLayout(3, false); + whereButtonsComp.setLayout(gridLayout); + addWhereClause = new Button(whereButtonsComp, SWT.PUSH | SWT.FLAT); + addWhereClause.setText(Labels.getString("ExtractionSOQLPage.addCondition")); //$NON-NLS-1$ + addWhereClause.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { - String whereField = fieldCombo.getText(); + String whereField = whereFieldCombo.getText(); String whereOper = operCombo.getText(); String whereValue = valueText.getText(); if (validateStr(whereField) && validateStr(whereOper)) { if (wherePart.length() == 0) { - wherePart.append("WHERE "); //$NON-NLS-1$ + wherePart.append("WHERE \n "); //$NON-NLS-1$ } else { - wherePart.append("AND "); //$NON-NLS-1$ + wherePart.append("\n AND "); //$NON-NLS-1$ } boolean isSingleQuoteValue = isSingleQuoteValue(whereField); - wherePart.append(whereField); wherePart.append(SPACE); wherePart.append(getOperValue(whereOper)); @@ -327,25 +422,26 @@ public void widgetSelected(SelectionEvent e) { if (isMultiPickList) { wherePart.append(CLOSE_BRACKET); } - } - generateSOQLText(); + updateSoQLTextAndButtons(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); + setAddWhereButtonState(); - Button clearWhere = new Button(whereComp, SWT.PUSH | SWT.FLAT); - clearWhere.setText(Labels.getString("ExtractionSOQLPage.clearAllConditions")); //$NON-NLS-1$ + clearAllWhereClauses = new Button(whereButtonsComp, SWT.PUSH | SWT.FLAT); + clearAllWhereClauses.setText(Labels.getString("ExtractionSOQLPage.clearAllConditions")); //$NON-NLS-1$ data = new GridData(); data.horizontalSpan = 2; - clearWhere.setLayoutData(data); - clearWhere.addSelectionListener(new SelectionListener() { + clearAllWhereClauses.setLayoutData(data); + setClearWhereButtonState(); + clearAllWhereClauses.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { wherePart = new StringBuffer(); - generateSOQLText(); + updateSoQLTextAndButtons(); } @Override @@ -363,8 +459,7 @@ public void widgetDefaultSelected(SelectionEvent e) {} @Override public void widgetSelected(SelectionEvent e) { fieldViewer.setAllChecked(true); - generateFieldPart(); - generateSOQLText(); + updateSoQLTextAndButtons(); } @Override @@ -377,42 +472,84 @@ public void widgetDefaultSelected(SelectionEvent e) {} @Override public void widgetSelected(SelectionEvent e) { fieldViewer.setAllChecked(false); - generateFieldPart(); - generateSOQLText(); + updateSoQLTextAndButtons(); } @Override public void widgetDefaultSelected(SelectionEvent e) {} }); + setClearWhereButtonState(); new Label(builderComp, SWT.NONE); - // the bottow separator - Label labelSeparatorBottom = new Label(comp, SWT.SEPARATOR | SWT.HORIZONTAL); + // the bottom separator + Label labelSeparator = new Label(comp, SWT.SEPARATOR | SWT.HORIZONTAL); data = new GridData(GridData.FILL_HORIZONTAL); - labelSeparatorBottom.setLayoutData(data); + labelSeparator.setLayoutData(data); Label messageLabel = new Label(comp, SWT.NONE); messageLabel.setText(Labels.getString("ExtractionSOQLPage.queryBelowMsg")); //$NON-NLS-1$ - soqlText = new Text(comp, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL); + soqlText = new Text(comp, SWT.MULTI | SWT.WRAP | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); data = new GridData(GridData.FILL_BOTH); data.heightHint = 80; - data.widthHint = 250; - soqlText.setLayoutData(data); + soqlText.addModifyListener(new ModifyListener() { + @Override + public void modifyText(ModifyEvent arg0) { + updateWherePart(); + setPageComplete(); + } + }); + + labelSeparator = new Label(comp, SWT.SEPARATOR | SWT.HORIZONTAL); + data = new GridData(GridData.FILL_HORIZONTAL); + labelSeparator.setLayoutData(data); setControl(comp); + setupPage(); + } + + private void setAddWhereButtonState() { + if (this.whereFieldCombo.getText() != null + && !this.whereFieldCombo.getText().isBlank() + && this.operCombo.getText() != null + && !this.operCombo.getText().isBlank() + && this.valueText.getText() != null + && !this.valueText.getText().isBlank() + && this.soqlText.getText() != null + && !this.soqlText.getText().isBlank()) { + this.addWhereClause.setEnabled(true); + } else { + this.addWhereClause.setEnabled(false); + } + } + + private void setClearWhereButtonState() { + if (this.wherePart.length() == 0 || this.wherePart.toString().isBlank()) { + this.clearAllWhereClauses.setEnabled(false); + } else { + this.clearAllWhereClauses.setEnabled(true); + } } private String getOperValue(String operation) { return operationMap.get(operation); } + + private void preserveFieldViewerCheckedItems() { + Field field = (Field)fieldViewer.getElementAt(0); + int i = 0; + while (field != null) { + fieldViewer.setChecked(field, selectedFieldsInFieldViewer.contains(field)); + field = (Field)fieldViewer.getElementAt(++i); + } + } private boolean isSingleQuoteValue(String fieldName) { Field field; - for (int i = 0; i < fields.length; i++) { - field = fields[i]; + for (int i = 0; i < fieldsInSObject.length; i++) { + field = fieldsInSObject[i]; if (field.getName().equals(fieldName)) { switch (field.getType()) { case _boolean: @@ -448,60 +585,116 @@ private boolean isMultiPicklistOper(String value) { return (value.equals("includes") || value.equals("excludes")); } - private void generateFieldPart() { - Object[] fields = fieldViewer.getCheckedElements(); - fieldPart = new StringBuffer(); - Field field; - for (int i = 0; i < fields.length; i++) { - field = (Field)fields[i]; - fieldPart.append(field.getName()); - if ((i + 1) < fields.length) { - fieldPart.append(", "); //$NON-NLS-1$ - + private void generateSelectFromPart() { + Field field = (Field)fieldViewer.getElementAt(0); + int i = 0; + while (field != null) { + if (fieldViewer.getChecked(field)) { + if (!selectedFieldsInFieldViewer.contains(field)) { + selectedFieldsInFieldViewer.add(field); + } + } else { + selectedFieldsInFieldViewer.remove(field); } + field = (Field)fieldViewer.getElementAt(++i); } - fieldPart.append(" "); //$NON-NLS-1$ + selectFieldsClausePart = new StringBuffer(); + for (Field selectedField : selectedFieldsInFieldViewer) { + selectFieldsClausePart.append(selectedField.getName()); + selectFieldsClausePart.append(", "); //$NON-NLS-1$ + } + if (selectFieldsClausePart.length() > 0) { + selectFieldsClausePart = new StringBuffer(selectFieldsClausePart.substring(0, selectFieldsClausePart.length()-2)); + selectFieldsClausePart.append(" "); + } } private boolean validateStr(String str) { if (str != null && str.length() > 0) { return true; } return false; + } + protected boolean setupPagePostLogin() { + initializeSOQLText(); + return true; } - public void initializeSOQLText() { + private void initializeSOQLText() { logger.debug(Labels.getString("ExtractionSOQLPage.initializeMsg")); //$NON-NLS-1$ - Config config = controller.getConfig(); + AppConfig appConfig = controller.getAppConfig(); + String entityStr = appConfig.getString(AppConfig.PROP_ENTITY); + if (entityStr == null || entityStr.isBlank()) { + return; + } DescribeSObjectResult result = controller.getFieldTypes(); - fields = result.getFields(); - fieldViewer.setInput(fields); - fieldCombo.removeAll(); + fieldsInSObject = result.getFields(); + if (appConfig.getBoolean(AppConfig.PROP_SORT_EXTRACT_FIELDS)) { + Arrays.sort(fieldsInSObject, new Comparator(){ + @Override + public int compare(Field f1, Field f2) + { + return f1.getName().compareTo(f2.getName()); + } + }); + } + fieldViewer.setInput(fieldsInSObject); + updateWhereFieldComboList(null); + builderComp.layout(); + whereComp.layout(); + fromEntityPart = new StringBuffer("\nFROM ").append(appConfig.getString(AppConfig.PROP_ENTITY)).append(" "); //$NON-NLS-1$ //$NON-NLS-2$ + } + + private void updateWhereFieldComboList(String filterStr) { List fieldNames = new ArrayList(); - for (int i = 0; i < fields.length; i++) { + for (int i = 0; i < fieldsInSObject.length; i++) { // include all fields except encrypted string ones - if(FieldType.encryptedstring != fields[i].getType()) { - fieldNames.add(fields[i].getName()); + String name = fieldsInSObject[i].getName().toLowerCase(); + if(FieldType.encryptedstring != fieldsInSObject[i].getType()) { + if (filterStr == null + || filterStr.isEmpty() + || name.contains(filterStr.toLowerCase())) { + fieldNames.add(fieldsInSObject[i].getName()); + } } } String[] fieldNamesArray = fieldNames.toArray(new String[fieldNames.size()]); Arrays.sort(fieldNamesArray); - fieldCombo.setItems(fieldNamesArray); - builderComp.layout(); - whereComp.layout(); - - fromPart = new StringBuffer("FROM ").append(config.getString(Config.ENTITY)).append(" "); //$NON-NLS-1$ //$NON-NLS-2$ - + whereFieldCombo.setItems(fieldNamesArray); } - private void generateSOQLText() { - StringBuffer soql = new StringBuffer(SELECT); - soql.append(fieldPart); - soql.append(fromPart); - soql.append(wherePart); - soqlText.setText(soql.toString()); - + private void updateSoQLTextAndButtons() { + generateSelectFromPart(); + if (selectFieldsClausePart == null || selectFieldsClausePart.toString().isBlank()) { + // clear the SoQL text and where clause + wherePart = new StringBuffer(""); + soqlText.setText(""); + } else { + StringBuffer soql = new StringBuffer(SELECT); + soql.append(selectFieldsClausePart); + soql.append(fromEntityPart); + if (wherePart != null && !wherePart.toString().isBlank()) { + soql.append("\n"); + soql.append(wherePart); + } + soqlText.setText(soql.toString()); + } + setAddWhereButtonState(); + setClearWhereButtonState(); + } + + private void updateWherePart() { + String soqlStr = soqlText.getText(); + String whereClause = ""; + if (soqlStr != null && !soqlStr.isBlank()) { + soqlStr = soqlStr.toLowerCase(); + int idx = soqlStr.indexOf("where"); + if (idx >= 0) { + whereClause = soqlText.getText().substring(idx); + } + } + wherePart = new StringBuffer(whereClause); } public String getSOQL() { @@ -510,15 +703,16 @@ public String getSOQL() { @Override public IWizardPage getNextPage() { - finishPage(); - + String finishStepPageStr = ExtractionFinishPage.class.getSimpleName(); //$NON-NLS-1$ + ExtractionFinishPage finishStepPage = (ExtractionFinishPage)getWizard().getPage(finishStepPageStr); + if (!saveSoQL()) { + setPageComplete(false); + return null; // do not proceed to the next page if SoQL is not specified + } // get the next wizard page - ExtractionFinishPage finishPage = (ExtractionFinishPage)getWizard().getPage( - Labels.getString("FinishPage.title")); //$NON-NLS-1$ - if (finishPage != null) { - getWizard().getPage(Labels.getString("FinishPage.title")); - finishPage.setPageComplete(true); - return finishPage; + if (finishStepPage != null) { + finishStepPage.setupPage(); + return finishStepPage; } else { return super.getNextPage(); } @@ -526,12 +720,34 @@ public IWizardPage getNextPage() { /* * (non-Javadoc) - * @see com.salesforce.dataloader.ui.extraction.ExtractionPage#finishPage() + * @see com.salesforce.dataloader.ui.OperationPage#finishAllowed() */ @Override - public boolean finishPage() { - controller.getConfig().setValue(Config.EXTRACT_SOQL, getSOQL()); + public boolean finishAllowed() { + if (this.controller.getAppConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT)) { + // this page is not the finish page if extract status output is enabled + return false; + } + return saveSoQL(); + } + + private boolean saveSoQL() { + String soqlStr = getSOQL(); + if (soqlStr == null || soqlStr.isBlank()) { + return false; + } + controller.getAppConfig().setValue(AppConfig.PROP_EXTRACT_SOQL, soqlStr); if (!controller.saveConfig()) { return false; } return true; } + + @Override + public void setPageComplete() { + setPageComplete(saveSoQL()); + } + + @Override + public boolean canFlipToNextPage() { + return (this.controller.getAppConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT) && isPageComplete()); + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSettingsPage.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSettingsPage.java deleted file mode 100644 index 903204e35..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionSettingsPage.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.ui.extraction; - -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.ui.SettingsPage; - -/** - * @author Lexi Viripaeff - * @since 6.0 - */ -public class ExtractionSettingsPage extends SettingsPage { - - // logger - // private static Logger logger = Logger.getLogger(ExtractionSettingsPage.class); - - public ExtractionSettingsPage(Controller controller) { - super(controller); - } - - @Override - protected void loadDataSelectionPage(Controller controller) { - ExtractionDataSelectionPage selection = (ExtractionDataSelectionPage) getWizard().getPage("Data"); //$NON-NLS-1$ - selection.setInput(controller.getEntityDescribes()); - setPageComplete(true); - } -} diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionWizard.java b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionWizard.java index 8c40bc0b9..67ab00ff4 100644 --- a/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionWizard.java +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/ExtractionWizard.java @@ -29,12 +29,15 @@ import java.io.*; import java.lang.reflect.InvocationTargetException; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.eclipse.jface.dialogs.ProgressMonitorDialog; +import org.eclipse.jface.wizard.WizardPage; import org.eclipse.swt.SWT; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.ui.*; @@ -43,7 +46,7 @@ */ public class ExtractionWizard extends BaseWizard { - private static final Logger logger = Logger.getLogger(ExtractionWizard.class); + private static final Logger logger = DLLogManager.getLogger(ExtractionWizard.class); /** * ExtractionWizard constructor @@ -57,13 +60,13 @@ protected ExtractionWizard(Controller controller, OperationInfo opInfo) { } @Override - protected ExtractionPage setPages() { + protected WizardPage setPages() { final Controller controller = getController(); addPage(new ExtractionDataSelectionPage(controller )); ExtractionPage soqlPage = new ExtractionSOQLPage(controller); addPage(soqlPage); - ExtractionPage finishPage = soqlPage; - if (getConfig().getBoolean(Config.ENABLE_EXTRACT_STATUS_OUTPUT)) { + WizardPage finishPage = soqlPage; + if (getConfig().getBoolean(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT)) { //need to reference the finish page for performFinish() finishPage = new ExtractionFinishPage(controller); addPage(finishPage); @@ -72,8 +75,8 @@ protected ExtractionPage setPages() { } @Override - protected ExtractionPage getFinishPage() { - return (ExtractionPage)super.getFinishPage(); + protected OperationPage getFinishPage() { + return (OperationPage)super.getFinishPage(); } private boolean validateExtractionPath(String filePath) { @@ -118,11 +121,11 @@ private boolean validateExtractionPath(String filePath) { @Override public boolean performFinish() { - if (!validateExtractionPath(getConfig().getString(Config.DAO_NAME))) { + if (!validateExtractionPath(getConfig().getString(AppConfig.PROP_DAO_NAME))) { return false; } - if (!getFinishPage().finishPage()) { + if (!getFinishPage().finishAllowed()) { return false; } @@ -146,11 +149,13 @@ public boolean performFinish() { return false; } - return true; + return getController().isLastOperationSuccessful() && closeWizardPagePostSuccessfulFinish(); } @Override - protected SettingsPage createSettingsPage() { - return new ExtractionSettingsPage(getController()); + protected LoginPage createLoginPage() { + LoginPage loginPage = new LoginPage(getController()); + loginPage.setNextPageName(ExtractionDataSelectionPage.class.getSimpleName()); + return loginPage; } } diff --git a/src/main/java/com/salesforce/dataloader/ui/extraction/FieldFilter.java b/src/main/java/com/salesforce/dataloader/ui/extraction/FieldFilter.java new file mode 100644 index 000000000..64d0e9443 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/extraction/FieldFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui.extraction; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.eclipse.swt.widgets.Text; + +import com.sforce.soap.partner.Field; + +public class FieldFilter extends ViewerFilter { + private Text searchText; + + public FieldFilter(Text search) { + super(); + this.searchText = search; + } + /** + * Returns whether the specified element passes this filter + * + * @param arg0 + * the viewer + * @param arg1 + * the parent element + * @param arg2 + * the element + * @return boolean + */ + @Override + public boolean select(Viewer arg0, Object arg1, Object arg2) { + + Field field = (Field)arg2; + String fieldName = field.getName(); + String fieldLabel = field.getLabel(); + return filterBySearchText(fieldName, fieldLabel); + } + + private boolean filterBySearchText(String fieldName, String fieldLabel) { + String searchText = this.searchText.getText(); + if (searchText != null && !searchText.isEmpty()) { + searchText = searchText.toLowerCase(); + if ((fieldName != null && fieldName.toLowerCase().contains(searchText)) + || (fieldLabel != null && fieldLabel.toLowerCase().contains(searchText))) { + return true; + } else { + return false; + } + } else { // no search text specified + return true; + } + } + +} diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDragListener.java b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDragListener.java index 4b147f6a9..afcb99828 100644 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDragListener.java +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDragListener.java @@ -63,15 +63,15 @@ public void dragFinished(DragSourceEvent event) { IStructuredSelection selection = (IStructuredSelection)viewer.getSelection(); Field[] fields = (Field[])viewer.getInput(); ArrayList fieldList = new ArrayList(Arrays.asList(fields)); - for (Iterator it = selection.iterator(); it.hasNext();) { + for (Iterator it = selection.iterator(); it.hasNext();) { Field eventField = (Field)it.next(); fieldList.remove(eventField); } - Field[] newFields = fieldList.toArray(new Field[fieldList.size()]); - dlg.setFields(newFields); + Field[] unmappedSobjectFields = fieldList.toArray(new Field[fieldList.size()]); + dlg.setUnmappedSobjectFields(unmappedSobjectFields); - viewer.setInput(newFields); + viewer.setInput(unmappedSobjectFields); viewer.refresh(); } } catch (Exception e) { diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropActionDialog.java b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropActionDialog.java new file mode 100644 index 000000000..f27411f0a --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropActionDialog.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui.mapping; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.*; + +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.ui.Labels; +import com.salesforce.dataloader.ui.WizardDialog; +import com.salesforce.dataloader.ui.mapping.MappingDropAdapter.MAPPING_CHOICE; + +public class MappingDropActionDialog extends WizardDialog { + private Button replace; + private Button add; + private Button cancel; + private Label label; + private MappingDropAdapter dropAdapter; + private String csvField, currentlyMappedSforceFields; + + /** + * InputDialog constructor + * + * @param parent + * the parent + */ + public MappingDropActionDialog(MappingDropAdapter dropAdapter, Controller controller, + String csvField, String currentlyMappedSforceFields, String newSforceField) { + super(dropAdapter.getMappingDialog().getParent(), controller); + this.setText(Labels.getFormattedString(this.getClass().getSimpleName() + ".title", + new String[] {csvField, newSforceField})); //$NON-NLS-1$ + this.dropAdapter = dropAdapter; + this.csvField = csvField; + this.currentlyMappedSforceFields = currentlyMappedSforceFields; + } + + /** + * Creates the dialog's contents + * + * @param shell + * the dialog window + */ + protected void createContents(final Shell shell) { + + final int WIDTH_HINT = 500; + GridLayout layout = new GridLayout(1, false); + layout.verticalSpacing = 10; + shell.setLayout(layout); + + label = new Label(shell, SWT.WRAP); + label.setText(Labels.getFormattedString("MappingDropActionDialog.message", + new String[] {csvField, currentlyMappedSforceFields})); + GridData labelData = new GridData(GridData.FILL_HORIZONTAL); + labelData.widthHint = WIDTH_HINT; + label.setLayoutData(labelData); + + //the bottom separator + Label labelSeparatorBottom = new Label(shell, SWT.SEPARATOR | SWT.HORIZONTAL); + GridData sepData = new GridData(GridData.FILL_HORIZONTAL); + labelSeparatorBottom.setLayoutData(sepData); + + //buttons + Composite comp1 = new Composite(shell, SWT.NONE); + comp1.setLayout(new GridLayout(3, false)); + GridData comp1Data = new GridData(GridData.FILL_HORIZONTAL); + comp1Data.widthHint = WIDTH_HINT; + comp1.setLayoutData(comp1Data); + + replace = new Button(comp1, SWT.PUSH | SWT.FLAT); + replace.setText(Labels.getString("MappingDropActionDialog.replaceAction")); //$NON-NLS-1$ + replace.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + dropAdapter.performDropAction(MAPPING_CHOICE.REPLACE); + // replace current mapping with new + shell.close(); + } + }); + GridData buttonData = new GridData(); + replace.setLayoutData(buttonData); + + add = new Button(comp1, SWT.PUSH | SWT.FLAT); + add.setText(Labels.getString("MappingDropActionDialog.addAction")); //$NON-NLS-1$ + add.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + dropAdapter.performDropAction(MAPPING_CHOICE.ADD); + // add new mapping + shell.close(); + } + }); + buttonData = new GridData(); + add.setLayoutData(buttonData); + + cancel = new Button(comp1, SWT.PUSH | SWT.FLAT); + cancel.setText(Labels.getString("UI.cancel")); //$NON-NLS-1$ + cancel.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + dropAdapter.performDropAction(MAPPING_CHOICE.CANCEL); + // cancel operation, keep current mappng as-is + shell.close(); + } + }); + buttonData = new GridData(GridData.FILL_HORIZONTAL | GridData.HORIZONTAL_ALIGN_END); + buttonData.widthHint = 75; + cancel.setLayoutData(buttonData); + + shell.setDefaultButton(replace); + } +} diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropAdapter.java b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropAdapter.java index ae37fb211..77298fc29 100644 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropAdapter.java +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingDropAdapter.java @@ -34,6 +34,7 @@ import org.eclipse.swt.dnd.TextTransfer; import org.eclipse.swt.dnd.TransferData; +import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.ui.MappingDialog; /** @@ -43,42 +44,75 @@ * @since 6.0 */ public class MappingDropAdapter extends ViewerDropAdapter { - - private final MappingDialog dlg; - - public MappingDropAdapter(TableViewer arg0, MappingDialog dlg) { + public enum MAPPING_CHOICE { + ADD, + REPLACE, + CANCEL; + } + private static final String MAPPING_DELIMITER = ", "; + private final MappingDialog mappingDialog; + private String currentSforceMappings; + private String sforceFieldToAddOrReplace; + private Map.Entry dropEntry; + private Controller controller; + + public MappingDropAdapter(TableViewer arg0, MappingDialog dlg, Controller controller) { super(arg0); - this.dlg = dlg; + this.mappingDialog = dlg; + this.controller = controller; } + @SuppressWarnings("unchecked") @Override - public boolean performDrop(Object arg0) { - - TableViewer viewer = (TableViewer)getViewer(); - - @SuppressWarnings("unchecked") - Map.Entry entry = (Entry)getCurrentTarget(); - - //replenish the old - String oldSforce = entry.getValue(); - if (oldSforce != null && oldSforce.length() > 0) { - dlg.replenishField(oldSforce); + public boolean performDrop(Object arg0) { + this.dropEntry = (Entry)getCurrentTarget(); + this.currentSforceMappings = this.dropEntry.getValue(); + this.sforceFieldToAddOrReplace = (String)arg0; + mappingDialog.setIsDragNDropCancelled(false); + + if (this.currentSforceMappings == null || this.currentSforceMappings.isBlank()) { + // if no existing mapping, perform add action + performDropAction(MAPPING_CHOICE.ADD); + } else { + // ask user to add, replace, or cancel action if a mapping exists + MappingDropActionDialog selectDropActionDlg = new MappingDropActionDialog( + this, controller, + this.dropEntry.getKey(), + this.currentSforceMappings, + this.sforceFieldToAddOrReplace); + selectDropActionDlg.open(); } - - entry.setValue((String)arg0); - dlg.getMapper().putMapping(entry.getKey(), entry.getValue()); - - viewer.refresh(); - dlg.packMappingColumns(); - - return true; } + + public void performDropAction(MAPPING_CHOICE choice) { + String newSforceMappings = this.sforceFieldToAddOrReplace; + if (choice == MAPPING_CHOICE.CANCEL) { + mappingDialog.setIsDragNDropCancelled(true); + return; + } + if (this.currentSforceMappings != null && !this.currentSforceMappings.isBlank()) { + if (choice == MAPPING_CHOICE.ADD) { + // add to existing mappings + newSforceMappings = this.currentSforceMappings + MAPPING_DELIMITER + this.sforceFieldToAddOrReplace; + } else { // choice == MAPPING_CHOICE.REPLACE + //add replaced Salesforce fields in sforceFieldsViewer so that they are available for mapping to other fields + mappingDialog.replenishMappedSforceFields(this.currentSforceMappings); + } + } + this.dropEntry.setValue(newSforceMappings); + mappingDialog.getMapper().putMapping(this.dropEntry.getKey(), this.dropEntry.getValue()); + mappingDialog.packMappingColumns(); + } @Override public boolean validateDrop(Object arg0, int arg1, TransferData arg2) { return TextTransfer.getInstance().isSupportedType(arg2); } + + public MappingDialog getMappingDialog() { + return this.mappingDialog; + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingViewerComparator.java b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingViewerComparator.java new file mode 100644 index 000000000..2b7af6fd1 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingViewerComparator.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui.mapping; + +import java.util.Map.Entry; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; + +import com.salesforce.dataloader.ui.MappingDialog; + +/** + * This class implements the sorting for the SforceTable + */ +public class MappingViewerComparator extends ViewerComparator { + private static final int ASCENDING = 0; + private static final int DESCENDING = 1; + + private int column = 0; + private int direction = ASCENDING; + + /** + * Does the sort. If it's a different column from the previous sort, do an ascending sort. If it's the same column + * as the last sort, toggle the sort direction. + * + * @param column + */ + public void doSort(int column) { + if (column == this.column) { + // Same column as last sort; toggle the direction + direction = 1 - direction; + } else { + // New column; do an ascending sort + this.column = column; + direction = ASCENDING; + } + } + + private int safeCompare(String s1, String s2) + { + if(s1 == null && s2 == null) + return 0; + if (s1 == null) + return -1; + if (s2 == null) + return 1; + return s1.compareToIgnoreCase(s2); + } + + /** + * Compares the object for sorting + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + int rc = 0; + @SuppressWarnings("unchecked") + Entry m1 = (Entry)e1; + @SuppressWarnings("unchecked") + Entry m2 = (Entry)e2; + + // Determine which column and do the appropriate sort + switch (column) { + case MappingDialog.MAPPING_DAO: + rc =safeCompare(m1.getKey(), m2.getKey()); + break; + case MappingDialog.MAPPING_SFORCE: + rc = safeCompare(m1.getValue(), m2.getValue()); + break; + } + + // If descending order, flip the direction + if (direction == DESCENDING) rc = -rc; + + return rc; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingViewerSorter.java b/src/main/java/com/salesforce/dataloader/ui/mapping/MappingViewerSorter.java deleted file mode 100644 index 6b4de1d58..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/MappingViewerSorter.java +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui.mapping; - -import java.util.Map.Entry; - -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerSorter; - -import com.salesforce.dataloader.ui.MappingDialog; - -/** - * This class implements the sorting for the SforceTable - */ -public class MappingViewerSorter extends ViewerSorter { - private static final int ASCENDING = 0; - private static final int DESCENDING = 1; - - private int column = 0; - private int direction = ASCENDING; - - /** - * Does the sort. If it's a different column from the previous sort, do an ascending sort. If it's the same column - * as the last sort, toggle the sort direction. - * - * @param column - */ - public void doSort(int column) { - if (column == this.column) { - // Same column as last sort; toggle the direction - direction = 1 - direction; - } else { - // New column; do an ascending sort - this.column = column; - direction = ASCENDING; - } - } - - private int safeCompare(String s1, String s2) - { - if(s1 == null && s2 == null) - return 0; - if (s1 == null) - return -1; - if (s2 == null) - return 1; - return collator.compare(s1, s2); - } - - /** - * Compares the object for sorting - */ - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - int rc = 0; - @SuppressWarnings("unchecked") - Entry m1 = (Entry)e1; - @SuppressWarnings("unchecked") - Entry m2 = (Entry)e2; - - // Determine which column and do the appropriate sort - switch (column) { - case MappingDialog.MAPPING_DAO: - rc =safeCompare(m1.getKey(), m2.getKey()); - break; - case MappingDialog.MAPPING_SFORCE: - rc = safeCompare(m1.getValue(), m2.getValue()); - break; - } - - // If descending order, flip the direction - if (direction == DESCENDING) rc = -rc; - - return rc; - } -} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDragListener.java b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDragListener.java index a70b945c0..26334efd6 100644 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDragListener.java +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDragListener.java @@ -43,11 +43,11 @@ */ public class SforceDragListener extends DragSourceAdapter { private final StructuredViewer viewer; - private final MappingDialog dlg; + private final MappingDialog mappingDialog; public SforceDragListener(StructuredViewer viewer, MappingDialog dialog) { this.viewer = viewer; - this.dlg = dialog; + this.mappingDialog = dialog; } /** @@ -59,17 +59,16 @@ public void dragFinished(DragSourceEvent event) { //if the gadget was moved, remove it from the source viewer try { - if (event.detail == DND.DROP_MOVE) { + if (event.detail == DND.DROP_MOVE && !mappingDialog.isDragActionCancelled()) { IStructuredSelection selection = (IStructuredSelection)viewer.getSelection(); - for (Iterator it = selection.iterator(); it.hasNext();) { + for (Iterator it = selection.iterator(); it.hasNext();) { @SuppressWarnings("unchecked") Map.Entry eventElem = (Entry)it.next(); eventElem.setValue(""); - dlg.getMapper().removeMapping(eventElem.getKey()); + mappingDialog.getMapper().removeMapping(eventElem.getKey()); } - + mappingDialog.packMappingColumns(); viewer.refresh(); - dlg.packMappingColumns(); } } catch (Exception e) { e.printStackTrace(); diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDropAdapter.java b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDropAdapter.java index 869580bed..b1f57ca3f 100644 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDropAdapter.java +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceDropAdapter.java @@ -50,15 +50,8 @@ public SforceDropAdapter(TableViewer arg0, MappingDialog dlg) { @Override public boolean performDrop(Object arg0) { - - TableViewer viewer = (TableViewer)getViewer(); - - dlg.replenishField((String) arg0); - - - viewer.refresh(); + dlg.replenishMappedSforceFields((String) arg0); dlg.packMappingColumns(); - return true; } diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceLabelProvider.java b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceLabelProvider.java index 22ea6b8fc..b2cc61abd 100644 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceLabelProvider.java +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceLabelProvider.java @@ -30,9 +30,13 @@ import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.swt.graphics.Image; +import com.salesforce.dataloader.client.DescribeRefObject; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dyna.ParentIdLookupFieldFormatter; import com.salesforce.dataloader.ui.MappingDialog; import com.sforce.soap.partner.Field; + /** * This class provides the labels for PlayerTable */ @@ -40,8 +44,7 @@ public class SforceLabelProvider implements ITableLabelProvider { // Constructs a PlayerLabelProvider - public SforceLabelProvider() { - + public SforceLabelProvider(Controller controller) { } @@ -62,16 +65,48 @@ public Image getColumnImage(Object arg0, int arg1) { @Override public String getColumnText(Object arg0, int arg1) { Field field = (Field) arg0; + boolean isReferenceField = false; + String[] referenceTos = field.getReferenceTo(); + if (referenceTos != null && referenceTos.length > 0) { + isReferenceField = true; + } String text = ""; switch (arg1) { case MappingDialog.FIELD_NAME: text = field.getName(); + if (isReferenceField && !text.contains(ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR)) { + text = field.getRelationshipName() + ParentIdLookupFieldFormatter.NEW_FORMAT_PARENT_IDLOOKUP_FIELD_SEPARATOR_CHAR + "Id"; + } break; case MappingDialog.FIELD_LABEL: text = field.getLabel(); break; case MappingDialog.FIELD_TYPE: text = field.getType().toString(); + if ("string".equalsIgnoreCase(text) || "textarea".equalsIgnoreCase(text)) { + text = text + + "(" + + field.getLength() + +")"; + } + if ("reference".equalsIgnoreCase(text)) { + text = "Lookup"; + if (isReferenceField) { + if (referenceTos.length >= DescribeRefObject.MAX_PARENT_OBJECTS_IN_REFERENCING_FIELD) { + text = text + " (" + referenceTos.length + " objects)"; + } else { + for (int i = 0; i < referenceTos.length; i++) { + String refEntityName = referenceTos[i]; + if (i == 0) { + text = text + " (" + refEntityName; + } else { + text = text + ", " + refEntityName; + } + } + text = text +")"; + } + } + } break; } return text; diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceViewerComparator.java b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceViewerComparator.java new file mode 100644 index 000000000..746f8cf67 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceViewerComparator.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.ui.mapping; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; + +import com.salesforce.dataloader.ui.MappingDialog; +import com.sforce.soap.partner.Field; + +/** + * This class implements the sorting for the SforceTable + */ +public class SforceViewerComparator extends ViewerComparator { + private static final int ASCENDING = 0; + private static final int DESCENDING = 1; + + private int column = 0; + private int direction = ASCENDING; + + /** + * Does the sort. If it's a different column from the previous sort, do an ascending sort. If it's the same column + * as the last sort, toggle the sort direction. + * + * @param column + */ + public void doSort(int column) { + if (column == this.column) { + // Same column as last sort; toggle the direction + direction = 1 - direction; + } else { + // New column; do an ascending sort + this.column = column; + direction = ASCENDING; + } + } + + /** + * Compares the object for sorting + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + int rc = 0; + Field f1 = (Field)e1; + Field f2 = (Field)e2; + + // Determine which column and do the appropriate sort + switch (column) { + case MappingDialog.FIELD_LABEL: + rc = f1.getLabel().compareToIgnoreCase(f2.getLabel()); + break; + case MappingDialog.FIELD_NAME: + rc = f1.getName().compareToIgnoreCase(f2.getName()); + break; + case MappingDialog.FIELD_TYPE: + rc = f1.getType().toString().compareToIgnoreCase(f2.getType().toString()); + break; + } + + + // If descending order, flip the direction + if (direction == DESCENDING) rc = -rc; + + return rc; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceViewerSorter.java b/src/main/java/com/salesforce/dataloader/ui/mapping/SforceViewerSorter.java deleted file mode 100644 index f39166211..000000000 --- a/src/main/java/com/salesforce/dataloader/ui/mapping/SforceViewerSorter.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.ui.mapping; - -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerSorter; - -import com.salesforce.dataloader.ui.MappingDialog; -import com.sforce.soap.partner.Field; - -/** - * This class implements the sorting for the SforceTable - */ -public class SforceViewerSorter extends ViewerSorter { - private static final int ASCENDING = 0; - private static final int DESCENDING = 1; - - private int column = 0; - private int direction = ASCENDING; - - /** - * Does the sort. If it's a different column from the previous sort, do an ascending sort. If it's the same column - * as the last sort, toggle the sort direction. - * - * @param column - */ - public void doSort(int column) { - if (column == this.column) { - // Same column as last sort; toggle the direction - direction = 1 - direction; - } else { - // New column; do an ascending sort - this.column = column; - direction = ASCENDING; - } - } - - /** - * Compares the object for sorting - */ - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - int rc = 0; - Field f1 = (Field)e1; - Field f2 = (Field)e2; - - // Determine which column and do the appropriate sort - switch (column) { - case MappingDialog.FIELD_LABEL: - rc = collator.compare(f1.getLabel(), f2.getLabel()); - break; - case MappingDialog.FIELD_NAME: - rc = collator.compare(f1.getName(), f2.getName()); - break; - case MappingDialog.FIELD_TYPE: - rc = collator.compare(f1.getType().toString(), f2.getType().toString()); - break; - } - - - // If descending order, flip the direction - if (direction == DESCENDING) rc = -rc; - - return rc; - } -} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/uiActions/HelpUIAction.java b/src/main/java/com/salesforce/dataloader/ui/uiActions/HelpUIAction.java index 016147730..9f649ab76 100644 --- a/src/main/java/com/salesforce/dataloader/ui/uiActions/HelpUIAction.java +++ b/src/main/java/com/salesforce/dataloader/ui/uiActions/HelpUIAction.java @@ -30,6 +30,7 @@ import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.ui.*; +import com.salesforce.dataloader.util.AppUtil; /** * @author Lexi Viripaeff @@ -50,11 +51,27 @@ public HelpUIAction(Controller controller) { public void run() { HyperlinkDialog dlg = new HyperlinkDialog(LoaderWindow.getApp().getShell(), controller); dlg.setText(Labels.getString("HelpUIAction.dlgTitle")); //$NON-NLS-1$ - dlg.setMessage(Labels.getString("HelpUIAction.dlgMsg")); //$NON-NLS-1$ - dlg.setLinkText(Labels.getString("HelpUIAction.dlgLinkText")); //$NON-NLS-1$ - dlg.setLinkURL(Labels.getString("HelpUIAction.dlgURL")); //$NON-NLS-1$ + dlg.setMessage( + getLoaderUpgradeMessage() + + System.getProperty("line.separator") + + Labels.getString("HelpUIAction.dlgMsg") + + System.getProperty("line.separator") + + Labels.getString("HelpUIAction.dlgLinkText") + ); //$NON-NLS-1$ dlg.setBoldMessage(Labels.getString("HelpUIAction.msgHeader")); //$NON-NLS-1$ dlg.open(); } + + private String getLoaderUpgradeMessage() { + String upgradeMsg = Labels.getString("HelpUIAction.latestVersion"); + if (!AppUtil.DATALOADER_VERSION.equals(controller.getLatestDownloadableDataLoaderVersion())) { + upgradeMsg = + Labels.getFormattedString("LoaderDownloadDialog.messageLineOne", + new String[] {controller.getLatestDownloadableDataLoaderVersion(), + AppUtil.DATALOADER_DOWNLOAD_URL}); + } + upgradeMsg += System.getProperty("line.separator") + System.getProperty("line.separator"); + return upgradeMsg; + } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/uiActions/LogoutUIAction.java b/src/main/java/com/salesforce/dataloader/ui/uiActions/LogoutUIAction.java index e75e99140..dbad65af3 100644 --- a/src/main/java/com/salesforce/dataloader/ui/uiActions/LogoutUIAction.java +++ b/src/main/java/com/salesforce/dataloader/ui/uiActions/LogoutUIAction.java @@ -48,6 +48,7 @@ public LogoutUIAction(Controller controller) { @Override public void run() { controller.logout(); + controller.updateLoaderWindowTitleAndCacheUserInfoForTheSession(); } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/ui/uiActions/OperationUIAction.java b/src/main/java/com/salesforce/dataloader/ui/uiActions/OperationUIAction.java index ba8f805c0..e810bf25d 100644 --- a/src/main/java/com/salesforce/dataloader/ui/uiActions/OperationUIAction.java +++ b/src/main/java/com/salesforce/dataloader/ui/uiActions/OperationUIAction.java @@ -43,14 +43,14 @@ public OperationUIAction(Controller controller, OperationInfo info) { this.controller = controller; this.opInfo = info; - setEnabled(info.isOperationAllowed(controller.getConfig())); + setEnabled(info.isOperationAllowed(controller.getAppConfig())); } @Override public void run() { this.controller.clearMapper(); - LoaderWizardDialog dlg = new LoaderWizardDialog(LoaderWindow.getApp().getShell(), this.opInfo - .instantiateWizard(this.controller)); + LoaderWizardDialog dlg = new LoaderWizardDialog(LoaderWindow.getApp().getShell(), + this.opInfo.getUIHelper().instantiateWizard(this.controller), this.controller.getAppConfig()); dlg.open(); } diff --git a/src/main/java/com/salesforce/dataloader/util/AppUtil.java b/src/main/java/com/salesforce/dataloader/util/AppUtil.java new file mode 100644 index 000000000..885b8d301 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/AppUtil.java @@ -0,0 +1,537 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Proxy.Type; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.TimeZone; + +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.sforce.ws.ConnectorConfig; +import com.sforce.ws.bind.CalendarCodec; + + +/** + * com.salesforce.dataloader.util + * + * @author xbian + * @since + */ +public class AppUtil { + + + public enum OSType { + WINDOWS, + LINUX, + MACOSX, + UNKNOWN + } + + public enum APP_RUN_MODE { + BATCH, + UI, + INSTALL, + ENCRYPT + } + public static final String COMMA = ","; + public static final String TAB = "\t"; + public static final String DATALOADER_VERSION; + public static final String DATALOADER_SHORT_VERSION; + public static final String MIN_JAVA_VERSION; + public static final String DATALOADER_DOWNLOAD_URL = "https://developer.salesforce.com/tools/data-loader"; + public static final int EXIT_CODE_NO_ERRORS = 0; + public static final int EXIT_CODE_CLIENT_ERROR = 1; + public static final int EXIT_CODE_SERVER_ERROR = 2; + public static final int EXIT_CODE_OPERATION_ERROR = 3; + public static final int EXIT_CODE_RESULTS_ERROR = 4; + + private static APP_RUN_MODE appRunMode = APP_RUN_MODE.UI; + private static Logger logger = null; + private static final ArrayList CONTENT_SOBJECT_LIST = new ArrayList(); + + static { + Properties versionProps = new Properties(); + try { + versionProps.load(AppUtil.class.getClassLoader().getResourceAsStream("com/salesforce/dataloader/version.properties")); + } catch (IOException e) { + e.printStackTrace(); + System.exit(EXIT_CODE_CLIENT_ERROR); + } + + DATALOADER_VERSION=versionProps.getProperty("dataloader.version"); + String[] versionParts = DATALOADER_VERSION.split("\\."); + DATALOADER_SHORT_VERSION=versionParts[0]; + MIN_JAVA_VERSION=versionProps.getProperty("java.min.version"); + CONTENT_SOBJECT_LIST.add("ContentNote".toLowerCase()); + } + + public static OSType getOSType() throws SecurityException { + String osName = System.getProperty(OS_NAME); + + if (osName != null) { + if (osName.contains("Windows")) { + return OSType.WINDOWS; + } + + if (osName.contains("Linux")) { + return OSType.LINUX; + } + + if (osName.contains("OS X")) { + return OSType.MACOSX; + } + + // determine another OS here + } + + return OSType.UNKNOWN; + } + + public static final String OS_NAME = "os.name"; + + public static String getFullPathOfJar(Class aClass) { + CodeSource codeSource = aClass.getProtectionDomain().getCodeSource(); + if (codeSource != null && codeSource.getLocation() != null) { + try { + String jarFilePath = codeSource.getLocation().toURI().toString(); + try { + jarFilePath = java.net.URLDecoder.decode(jarFilePath, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // ignore + } + return jarFilePath.substring(jarFilePath.indexOf('/')); + } catch (URISyntaxException e) { + return null; + } + } else { + String path = aClass.getResource(aClass.getSimpleName() + ".class").getPath(); + String jarFilePath = path.substring(path.indexOf(":") + 1, path.indexOf("!")); + try { + jarFilePath = URLDecoder.decode(jarFilePath, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException e) { + // fail silently; + } + return jarFilePath; + } + } + + public static String getDirContainingClassJar(Class aClass) { + File jarFile = new File(getFullPathOfJar(aClass)); + return jarFile.getParentFile().getAbsolutePath(); + } + + public static void extractFromJar(String extractionArtifact, File extractionDestination) throws IOException { + InputStream link; + link = AppUtil.class.getResourceAsStream(extractionArtifact); + String parentDirStr = extractionDestination.getAbsoluteFile().getParent(); + File parentDir = Paths.get(parentDirStr).toFile(); + Files.createDirectories(parentDir.getAbsoluteFile().toPath()); + if (extractionDestination.exists()) { + extractionDestination.delete(); + } + Files.copy(link, extractionDestination.getAbsoluteFile().toPath()); + } + + public static void extractDirFromJar(String extractionPrefix, String destDirName, boolean flatten) throws IOException, URISyntaxException { + if (logger == null) { + logger = DLLogManager.getLogger(AppUtil.class); + } + String jarPath = getFullPathOfJar(AppUtil.class); + java.util.jar.JarFile jarfile = new java.util.jar.JarFile(new java.io.File(jarPath)); //jar file path(here sqljdbc4.jar) + java.util.Enumeration enu= jarfile.entries(); + while(enu.hasMoreElements()) + { + java.util.jar.JarEntry jarEntry = enu.nextElement(); + + if (!jarEntry.toString().startsWith(extractionPrefix) + || jarEntry.isDirectory() + || jarEntry.getName().startsWith("\\.")) { + continue; + } + logger.debug(jarEntry.getName()); + + String childArtifactName = jarEntry.getName(); + if (flatten) { + childArtifactName = childArtifactName.substring(extractionPrefix.length()); + } + File extractionDestination = new File(destDirName, childArtifactName); + if (extractionDestination.exists()) { + logger.debug("File " + extractionDestination + " won't be extracted from jar as it already exists"); + continue; + } else { + extractionDestination.getParentFile().mkdirs(); + extractionDestination = new java.io.File(destDirName, childArtifactName); + } + extractionDestination.delete(); // just in case it is a dangling symlink + java.io.InputStream is = jarfile.getInputStream(jarEntry); + java.io.FileOutputStream fo = new java.io.FileOutputStream(extractionDestination); + while(is.available() > 0) + { + fo.write(is.read()); + } + fo.close(); + is.close(); + if (extractionDestination.getName().endsWith(".bat") + || extractionDestination.getName().endsWith(".sh") + || extractionDestination.getName().endsWith(".command") + || !extractionDestination.getName().contains("\\.")) { + extractionDestination.setExecutable(true); + } + } + jarfile.close(); + } + + public static APP_RUN_MODE getAppRunMode() { + return appRunMode; + } + + public static boolean isContentSObject(String sObjectName) { + return CONTENT_SOBJECT_LIST.contains(sObjectName.toLowerCase()); + } + + public static void showBanner() { + System.out.println(Messages.getMessage(AppUtil.class, "banner", DATALOADER_SHORT_VERSION, MIN_JAVA_VERSION)); + } + + private static final String OTHER_ARGS_KEY = "__OTHER_ARGS__"; + private static int otherArgsCount = 0; + public synchronized static Map convertCommandArgsArrayToArgMap(String[] argArray){ + Map commandArgsMap = new HashMap(); + if (argArray == null) { + return commandArgsMap; + } + + if (argArray != null) { + //Process name=value config setting + otherArgsCount = 0; + Arrays.stream(argArray).forEach(arg -> + { + String[] nameValuePair = arg.split("=", 2); + if (nameValuePair.length == 2) { + if (nameValuePair[0].equalsIgnoreCase(AppConfig.CLI_OPTION_RUN_MODE)) { + setAppRunMode(nameValuePair[1]); + } else { + commandArgsMap.put(nameValuePair[0], nameValuePair[1]); + } + } else { + commandArgsMap.put(OTHER_ARGS_KEY + otherArgsCount++, arg); + } + }); + } + if (getAppRunMode() == APP_RUN_MODE.BATCH) { + processArgsForBatchMode(argArray, commandArgsMap); + } + return commandArgsMap; + } + + + private static void processArgsForBatchMode(String[] args, Map argsMap) { + if (!argsMap.containsKey(AppConfig.CLI_OPTION_CONFIG_DIR_PROP) && args.length < 2) { + // config folder must be specified in the first argument + System.err.println( + "Usage: process [batch process bean id]\n" + + "\n" + + " configuration folder -- required -- folder that contains configuration files,\n" + + " i.e. config.properties, process-conf.xml, database-conf.xml\n" + + "\n" + + " batch process bean id -- optional -- id of a batch process bean in process-conf.xml,\n" + + " for example:\n" + + "\n" + + " process ../myconfigdir AccountInsert\n" + + "\n" + + " If process bean id is not specified, the value of the property process.name in config.properties\n" + + " will be used to run the process instead of process-conf.xml,\n" + + " for example:\n" + + "\n" + + " process ../myconfigdir"); + System.exit(EXIT_CODE_CLIENT_ERROR); + } + if (!argsMap.containsKey(AppConfig.CLI_OPTION_CONFIG_DIR_PROP)) { + argsMap.put(AppConfig.CLI_OPTION_CONFIG_DIR_PROP, args[0]); + } + if (!argsMap.containsKey(AppConfig.PROP_PROCESS_NAME) + && args.length > 2 + && !args[1].contains("=")) { + // second argument must be process name + argsMap.put(AppConfig.PROP_PROCESS_NAME, args[1]); + } + } + + private static void setAppRunMode(String modeStr) { + if (modeStr == null) return; + switch (modeStr) { + case "batch": + appRunMode = APP_RUN_MODE.BATCH; + break; + case "install": + appRunMode = APP_RUN_MODE.INSTALL; + break; + case "encrypt": + appRunMode = APP_RUN_MODE.ENCRYPT; + break; + default: + appRunMode = APP_RUN_MODE.UI; + } + } + + public static String[] convertCommandArgsMapToArgsArray(Map argsMap) { + if (argsMap == null) { + return null; + } + ArrayList argsList = new ArrayList(); + ArrayList argKeysList = new ArrayList(argsMap.keySet()); + Collections.sort(argKeysList); + for (String argKey : argKeysList) { + if (argKey.startsWith(OTHER_ARGS_KEY)) { + argsList.add(argsMap.get(argKey)); + } else { + String argVal = argsMap.get(argKey); + argsList.add(argKey + "=" + argVal); + } + } + return argsList.toArray(new String[0]); + } + + public static boolean isRunningOnMacOS() { + return getOSType() == OSType.MACOSX; + } + + public static boolean isRunningOnWindows() { + return getOSType() == OSType.WINDOWS; + } + + public static boolean isRunningOnLinux() { + return getOSType() == OSType.LINUX; + } + + // Run a command in the native system while redirecting its stdout and stderr + public static int exec(List command, String exceptionMessage) { + if (logger == null) { + logger = DLLogManager.getLogger(AppUtil.class); + } + ProcessBuilder processBuilder = new ProcessBuilder(command); + processBuilder.redirectErrorStream(true); + if (exceptionMessage == null) { + exceptionMessage = Messages.getString("AppUtil.processExecutionError"); + } + int exitVal = -1; + try { + Process process = processBuilder.start(); + InputStream is = process.getInputStream(); + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line; + + while ((line = br.readLine()) != null) { + System.out.println(line); + } + + exitVal = process.waitFor(); + } catch (IOException | InterruptedException e) { + // TODO Auto-generated catch block + logger.error(exceptionMessage, e); + } + return exitVal; + } + + public static boolean isValidHttpsUrl(String url) { + try { + // check if it is a valid url + URI uri = new URL(url).toURI(); + // check if it is https protocol + return "https".equalsIgnoreCase(uri.getScheme()); + } + catch (Exception e) { + return false; + } + } + public static void validateAuthenticationHostDomainUrlAndThrow(String url) { + if (!isValidHttpsUrl(url)) { + throw new RuntimeException("Dataloader only supports Authentication host domain URL that uses https protocol:" + url); + } + } + + public static String getURLStrFromDomainName(String domainName) { + if (domainName == null) { + return null; + } + if (domainName.contains("://")) { + return domainName; + } + return "https://" + domainName; + } + + public static String serializeToJson(Map nameValueMap) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + mapper.setDateFormat(CalendarCodec.getDateFormat()); + return mapper.writeValueAsString(nameValueMap); + } + + public static T deserializeJsonToObject (InputStream in, Class tmpClass) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + // By default, ObjectMapper generates Calendar instances with UTC TimeZone. + // Here, override that to "GMT" to better match the behavior of the WSC XML parser. + mapper.setTimeZone(TimeZone.getTimeZone("GMT")); + return mapper.readValue(in, tmpClass); + } + + private static final String SYSPROP_USE_SYSTEM_PROXIES = "java.net.useSystemProxies"; + public static Proxy getSystemHttpsProxy(String[] args) { + if (logger == null) { + logger = DLLogManager.getLogger(AppUtil.class); + } + Map argsMap = convertCommandArgsArrayToArgMap(args); + if (getAppRunMode() == APP_RUN_MODE.BATCH + || getAppRunMode() == APP_RUN_MODE.ENCRYPT + || (argsMap.containsKey(AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH) + && "true".equalsIgnoreCase(argsMap.get(AppConfig.CLI_OPTION_SWT_NATIVE_LIB_IN_JAVA_LIB_PATH)))) { + // do not check for system proxy settings + // if run mode is batch or encrypt + // or if on the 2nd iteration of the UI mode + return null; + } + + System.setProperty(SYSPROP_USE_SYSTEM_PROXIES, "true"); + logger.debug("detecting proxies"); + List proxyList = null; + java.net.Proxy proxy = null; + + try { + ProxySelector ps = ProxySelector.getDefault(); + if (ps != null) { + proxyList = ps.select(new URI("https://www.salesforce.com")); + } + } catch (URISyntaxException e) { + e.printStackTrace(); + } + if (proxyList != null) { + for (Iterator iter = proxyList.iterator(); iter.hasNext();) { + proxy = (java.net.Proxy) iter.next(); + logger.debug("System proxy type: " + proxy.type()); + if (proxy.type() != Type.HTTP) { + continue; + } + + InetSocketAddress addr = (InetSocketAddress) proxy.address(); + + if (addr == null) { + logger.debug("No system proxy"); + } else { + logger.debug("System proxy hostname: " + addr.getHostName()); + logger.debug("System proxy port: " + addr.getPort()); + break; + } + } + } + return proxy; + } + + public static void setConnectorConfigProxySettings(AppConfig appConfig, ConnectorConfig connConfig) { + if (logger == null) { + logger = DLLogManager.getLogger(AppUtil.class); + } + // proxy properties + try { + String proxyHost = appConfig.getString(AppConfig.PROP_PROXY_HOST); + int proxyPort = appConfig.getInt(AppConfig.PROP_PROXY_PORT); + if (proxyHost != null && proxyHost.length() > 0 && proxyPort > 0) { + logger.info(Messages.getFormattedString( + "AppUtil.sforceLoginProxyDetail", new String[] { proxyHost, String.valueOf(proxyPort) })); //$NON-NLS-1$ + connConfig.setProxy(proxyHost, proxyPort); + + String proxyUsername = appConfig.getString(AppConfig.PROP_PROXY_USERNAME); + if (proxyUsername != null && proxyUsername.length() > 0) { + logger.info(Messages.getFormattedString("AppUtil.sforceLoginProxyUser", proxyUsername)); //$NON-NLS-1$ + connConfig.setProxyUsername(proxyUsername); + + String proxyPassword = appConfig.getString(AppConfig.PROP_PROXY_PASSWORD); + if (proxyPassword != null && proxyPassword.length() > 0) { + logger.info(Messages.getString("AppUtil.sforceLoginProxyPassword")); //$NON-NLS-1$ + connConfig.setProxyPassword(proxyPassword); + } else { + connConfig.setProxyPassword(""); + logger.info("no proxy password"); + } + } + + String proxyNtlmDomain = appConfig.getString(AppConfig.PROP_PROXY_NTLM_DOMAIN); + if (proxyNtlmDomain != null && proxyNtlmDomain.length() > 0) { + logger.info(Messages.getFormattedString("AppUtil.sforceLoginProxyNtlm", proxyNtlmDomain)); //$NON-NLS-1$ + connConfig.setNtlmDomain(proxyNtlmDomain); + } + } + } catch (ParameterLoadException e) { + logger.error(e.getMessage()); + } + if (appConfig.getBoolean(AppConfig.PROP_DEBUG_MESSAGES)) { + connConfig.setTraceMessage(true); + connConfig.setPrettyPrintXml(true); + String filename = appConfig.getString(AppConfig.PROP_DEBUG_MESSAGES_FILE); + if (filename.length() > 0) { + try { + connConfig.setTraceFile(filename); + } catch (FileNotFoundException e) { + logger.warn(Messages.getFormattedString("Client.errorMsgDebugFilename", filename)); + } + } + } + + } +} diff --git a/src/main/java/com/salesforce/dataloader/util/DAORowUtil.java b/src/main/java/com/salesforce/dataloader/util/DAORowUtil.java index 0e1fd121e..17b76da6e 100644 --- a/src/main/java/com/salesforce/dataloader/util/DAORowUtil.java +++ b/src/main/java/com/salesforce/dataloader/util/DAORowUtil.java @@ -30,7 +30,10 @@ import java.util.*; import com.salesforce.dataloader.model.Row; -import org.apache.log4j.Logger; +import com.salesforce.dataloader.model.TableRow; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; import com.salesforce.dataloader.action.progress.ILoaderProgress; import com.salesforce.dataloader.action.visitor.DAOSizeVisitor; @@ -54,7 +57,7 @@ public static DAORowUtil get() { return INSTANCE; } - static Logger logger = Logger.getLogger(DAORowUtil.class); + static Logger logger = DLLogManager.getLogger(DAORowUtil.class); /** * Utility function for calculating the total number of rows available to current DAO instance @@ -64,7 +67,7 @@ public static int calculateTotalRows(DataReader dataReader) throws DataAccessObj try { //visit the rows DAOSizeVisitor visitor = new DAOSizeVisitor(); - for (Row row = dataReader.readRow(); isValidRow(row); row = dataReader.readRow()) { + for (TableRow row = dataReader.readTableRow(); isValidTableRow(row); row = dataReader.readTableRow()) { visitor.visit(row); } @@ -89,6 +92,14 @@ public static boolean isValidRow(Row row) { return true; } + /** + * @param row + * @return true if row is valid + */ + public static boolean isValidTableRow(TableRow row) { + if (row == null) { return false; } + return true; + } /** * @param row * @return true if row is valid @@ -106,34 +117,34 @@ public static boolean isValidRow(List row) { */ public static String validateColumns(DataAccessObject dao) { HashSet uniqueHeaders = new HashSet(); - String warning = null; + String error = null; for (String header : dao.getColumnNames()) { if (header == null || header.length() == 0) { - warning = Messages.getString("RowUtil.warningEmptyColumn"); //$NON-NLS-1$ + error = Messages.getString("RowUtil.warningEmptyColumn"); //$NON-NLS-1$ break; } else if (uniqueHeaders.contains(header)) { - warning = Messages.getFormattedString("RowUtil.warningDuplicateColumn", header); //$NON-NLS-1$ + error = Messages.getFormattedString("RowUtil.warningDuplicateColumn", header); //$NON-NLS-1$ break; } uniqueHeaders.add(header); } - if (warning != null) { - logger.warn(warning); + if (error != null) { + logger.error(error); } - return warning; + return error; } - public void skipRowToStartOffset(Config cfg, DataReader rdr, ILoaderProgress mon, boolean updateProgress) + public void skipRowToStartOffset(AppConfig cfg, DataReader rdr, ILoaderProgress mon, boolean updateProgress) throws LoadException { try { - cfg.setValue(LastRun.LAST_LOAD_BATCH_ROW, 0); + cfg.setValue(LastRunProperties.LAST_LOAD_BATCH_ROW, 0); rowToStart(cfg, rdr); if (updateProgress) { // set the last processed value to the starting row int currentRow = rdr.getCurrentRowNumber(); if (mon != null && currentRow > 0) mon.worked(currentRow); - cfg.setValue(LastRun.LAST_LOAD_BATCH_ROW, currentRow); + cfg.setValue(LastRunProperties.LAST_LOAD_BATCH_ROW, currentRow); cfg.saveLastRun(); } } catch (final DataAccessObjectException e) { @@ -142,6 +153,46 @@ public void skipRowToStartOffset(Config cfg, DataReader rdr, ILoaderProgress mon handleError(e, "errorLastRun"); } } + + public static String getPhoneFieldValue(String phoneValue, String localeStr) { + + + // The field is a phone field. + // Per documentation at https://help.salesforce.com/s/articleView?id=000385963&type=1 + // if Locale is set to English (United States) or English (Canada), + // 10-digit phone numbers and 11-digit numbers that start with “1” are + // formatted as (800) 555-1212. + // + // Locale codes "en_US" and "en_CA" obtained from + // https://docs.oracle.com/cd/E23824_01/html/E26033/glset.html + + if (localeStr == null) { + localeStr = Locale.getDefault().toString(); + } + if (phoneValue == null + || !("en_US".equalsIgnoreCase(localeStr) || "en_CA".equalsIgnoreCase(localeStr)) + || phoneValue.length() < 10 + || phoneValue.length() > 11 + ) { + return phoneValue; + } + + try { + Long.parseUnsignedLong(phoneValue); + } catch (Exception ex) { + // phone number contains non-numeric characters + return phoneValue; + } + if (phoneValue.startsWith("+") || phoneValue.startsWith("-")) { + return phoneValue; + } + if (phoneValue.length() == 10) { // use the format (xxx) xxx-xxxx + phoneValue = phoneValue.replaceFirst("(\\d{3})(\\d{3})(\\d+)", "($1) $2-$3"); + } else if (phoneValue.length() == 11 && phoneValue.startsWith("1")) { // length 11 and starts with 1 + phoneValue = phoneValue.replaceFirst("(\\d{1})(\\d{3})(\\d{3})(\\d+)", "($2) $3-$4"); + } + return phoneValue; + } private void handleError(final Exception e, String msgKey) throws LoadException { final String errMsg = Messages.getMessage(getClass(), msgKey); @@ -152,11 +203,11 @@ private void handleError(final Exception e, String msgKey) throws LoadException /** * Set the dataReader to point to the row where load has to be started */ - private void rowToStart(Config cfg, DataReader daoReader) throws DataAccessObjectException { + private void rowToStart(AppConfig cfg, DataReader daoReader) throws DataAccessObjectException { // start at the correct row final int rowToStart; try { - rowToStart = cfg.getInt(Config.LOAD_ROW_TO_START_AT); + rowToStart = cfg.getInt(AppConfig.PROP_LOAD_ROW_TO_START_AT); } catch (final ParameterLoadException e) { return; } @@ -164,7 +215,7 @@ private void rowToStart(Config cfg, DataReader daoReader) throws DataAccessObjec // keep skipping over rows until we run into an invalid row or we have gotten // to the starting row while (daoReader.getCurrentRowNumber() < rowToStart) { - if (!DAORowUtil.isValidRow(daoReader.readRow())) break; + if (!DAORowUtil.isValidTableRow(daoReader.readTableRow())) break; } } } diff --git a/src/main/java/com/salesforce/dataloader/util/DLLogManager.java b/src/main/java/com/salesforce/dataloader/util/DLLogManager.java new file mode 100644 index 000000000..45f8fafb2 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/DLLogManager.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.util; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public class DLLogManager { + public static Logger getLogger(Class clazz) { + return LogManager.getLogger(clazz); + } +} diff --git a/src/main/java/com/salesforce/dataloader/util/DateOnlyCalendar.java b/src/main/java/com/salesforce/dataloader/util/DateOnlyCalendar.java new file mode 100644 index 000000000..3dacc1d86 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/DateOnlyCalendar.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +public class DateOnlyCalendar extends GregorianCalendar { + /** + * + */ + private static final long serialVersionUID = 1L; + static final TimeZone GMT_TZ = TimeZone.getTimeZone("GMT"); + static final Logger logger; + + static { + logger = DLLogManager.getLogger(DateOnlyCalendar.class); + } + + public DateOnlyCalendar() { + super(); + } + + private DateOnlyCalendar(TimeZone tz) { + // Use the timezone param to update the date by 1 in setDate() + super(tz); + } + + public void setTimeInMillis(long specifiedTimeInMilliSeconds) { + TimeZone myTimeZone = super.getTimeZone(); + + if (myTimeZone == null) { + logger.info("timezone is null"); + } else { + logger.info("Timezone is " + myTimeZone.getDisplayName()); + } + Calendar cal = Calendar.getInstance(myTimeZone); + cal.setTimeInMillis(specifiedTimeInMilliSeconds); + + if (!AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_GMT_FOR_DATE_FIELD_VALUE) + && myTimeZone != null) { + // Set hour, minute, second, and millisec to 0 (12:00AM) as it is date-only value + cal.set(Calendar.HOUR, 0); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + cal.set(Calendar.AM_PM, Calendar.AM); + + int timeZoneDifference = myTimeZone.getOffset(cal.getTimeInMillis()); + if (timeZoneDifference > 0) { + // timezone is ahead of GMT, compensate for it as server-side thinks it is in GMT. + cal.setTimeInMillis(cal.getTimeInMillis() + timeZoneDifference); + } + } + super.setTimeInMillis(cal.getTimeInMillis()); + } + + public static DateOnlyCalendar getInstance(TimeZone timeZone) { + if (AppConfig.getCurrentConfig().getBoolean(AppConfig.PROP_GMT_FOR_DATE_FIELD_VALUE)) { + timeZone = GMT_TZ; + } + return new DateOnlyCalendar(timeZone); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/util/ExceptionUtil.java b/src/main/java/com/salesforce/dataloader/util/ExceptionUtil.java index 14853c892..ee7124ace 100644 --- a/src/main/java/com/salesforce/dataloader/util/ExceptionUtil.java +++ b/src/main/java/com/salesforce/dataloader/util/ExceptionUtil.java @@ -36,7 +36,7 @@ public static String getStackTraceString(Throwable e) { StringBuilder stackTrace = new StringBuilder(); if (e.getStackTrace() != null) { for (StackTraceElement elem : e.getStackTrace()) { - stackTrace.append("\t" + elem.toString() + "\n"); + stackTrace.append(AppUtil.TAB + elem.toString() + "\n"); } } return stackTrace.toString(); diff --git a/src/main/java/com/salesforce/dataloader/util/ExitException.java b/src/main/java/com/salesforce/dataloader/util/ExitException.java new file mode 100644 index 000000000..0327e2d57 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/ExitException.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +public class ExitException extends RuntimeException { + private static final long serialVersionUID = 1L; + private int exitCode = AppUtil.EXIT_CODE_NO_ERRORS; + public ExitException(Throwable ex, int exitCode) { + super(ex); + this.exitCode = exitCode; + } + + public ExitException(String message, int exitCode) { + super(message); + this.exitCode = exitCode; + } + + public int getExitCode() { + return this.exitCode; + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/util/LoadRateCalculator.java b/src/main/java/com/salesforce/dataloader/util/LoadRateCalculator.java index 8254f39f9..78ef901de 100644 --- a/src/main/java/com/salesforce/dataloader/util/LoadRateCalculator.java +++ b/src/main/java/com/salesforce/dataloader/util/LoadRateCalculator.java @@ -40,44 +40,69 @@ public class LoadRateCalculator { // TODO: we can probably move all references to this code into a base ProgressMonitor class private Date startTime = null; - private int totalRecords; + private long totalRecordsInJob = 0; + private boolean started = false; - public LoadRateCalculator() {} - - public void start() { - this.startTime = new Date(); + public LoadRateCalculator() { + // do nothing } - public void setNumRecords(int numRecords) { - this.totalRecords = numRecords; + public synchronized void start(int numRecords) { + if (!started) { + started = true; + this.startTime = new Date(); + this.totalRecordsInJob = numRecords; + } } - public String calculateSubTask(int numProcessed, int numErrors) { + public String calculateSubTask(long processedRecordsInJob, long numErrorsInJob) { final Date currentLoadTime = new Date(); - final int numSuccess = numProcessed - numErrors; - final long currentPerMin = numSuccess * 60 * 60; - long rate; + final long numSuccessInJob = processedRecordsInJob - numErrorsInJob; + //final long currentPerMin = numSuccess * 60 * 60; + long hourlyProcessingRate; - final long totalElapsed = currentLoadTime.getTime() - this.startTime.getTime(); - if (totalElapsed == 0) { - rate = 0; + final long totalElapsedTimeInSec = (currentLoadTime.getTime() - this.startTime.getTime())/1000; + final long elapsedTimeInMinutes = totalElapsedTimeInSec / 60; + if (totalElapsedTimeInSec == 0) { + hourlyProcessingRate = 0; } else { - rate = currentPerMin / totalElapsed * 1000; + hourlyProcessingRate = (processedRecordsInJob * 60 * 60) / totalElapsedTimeInSec; } - long remainingSeconds = 0; - if (this.totalRecords > 0) { - // time_remaining = time_elapsed / percent_complete - time_elapsed - remainingSeconds = (long)(((double)this.totalRecords / numSuccess - 1.0) * totalElapsed) / 1000; + long remainingTimeInSec = 0; + long estimatedTotalTimeInSec = 0; + if (this.totalRecordsInJob > 0 && processedRecordsInJob > 0) { + // can estimate remaining time only if a few records are processed already. + estimatedTotalTimeInSec = (long) (totalElapsedTimeInSec * this.totalRecordsInJob / processedRecordsInJob); + remainingTimeInSec = estimatedTotalTimeInSec - totalElapsedTimeInSec; } - final long mins = remainingSeconds / 60; + final long remainingTimeInMinutes = remainingTimeInSec / 60; + final long remainingSeconds = remainingTimeInSec - remainingTimeInMinutes * 60; - final long seconds = remainingSeconds - mins * 60; - - return Messages.getMessage(getClass(), "processed", numProcessed, this.totalRecords, rate, mins, seconds, - numSuccess, - numErrors); + if (hourlyProcessingRate <= 0 || (remainingTimeInMinutes > 7 * 24 * 60)) { // processing time not calculated or imprecise + // LoadRateCalculator.processedTimeUnknown=Processed {0} of {1} total records. + // There are {2} successes and {3} errors. + return Messages.getMessage(getClass(), "processedTimeUnknown", + processedRecordsInJob, // {0} + this.totalRecordsInJob, // {1} + numSuccessInJob, // {2} + numErrorsInJob); // {3} + } + // LoadRateCalculator.processed=Processed {0} of {1} total records in {8} minutes, {7} seconds. + // There are {5} successes and {6} errors. \nRate: {2} records per hour. + // Estimated time to complete: {3} minutes and {4} seconds. + return Messages.getMessage(getClass(), "processed", + processedRecordsInJob, // {0} + this.totalRecordsInJob, // {1} + hourlyProcessingRate, // {2} + remainingTimeInMinutes, // {3} + remainingSeconds, // {4} + numSuccessInJob, // {5} + numErrorsInJob, // {6} + totalElapsedTimeInSec - (60 * elapsedTimeInMinutes), // {7} + elapsedTimeInMinutes // {8} + ); } } \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/util/LoggingUtil.java b/src/main/java/com/salesforce/dataloader/util/LoggingUtil.java new file mode 100644 index 000000000..d7222709c --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/LoggingUtil.java @@ -0,0 +1,193 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import javax.xml.parsers.FactoryConfigurationError; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.Appender; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.appender.RollingFileAppender; +import org.apache.logging.log4j.core.config.Configuration; +import org.apache.logging.log4j.core.config.Configurator; +import org.apache.logging.log4j.core.config.LoggerConfig; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.LinkedProperties; + +public class LoggingUtil { + + public static final String SYS_PROP_LOG4J2_CONFIG_FILE = "log4j2.configurationFile"; + private static final String LOG_CONF_DEFAULT_XML = "log-conf.xml"; + private static final String LOG_CONF_DEFAULT_PROPERTIES = "log4j2.properties"; + public static String LOG_CONF_DEFAULT = LOG_CONF_DEFAULT_PROPERTIES; + private static Logger logger = LogManager.getLogger(LoggingUtil.class); + + public static synchronized void initializeLog(Map argsMap) throws FactoryConfigurationError, IOException { + if (argsMap == null) { + argsMap = new HashMap(); + } + // check the environment variable for log4j + String log4jConfigFilePath = System.getenv("LOG4J_CONFIGURATION_FILE"); + if (log4jConfigFilePath == null || log4jConfigFilePath.isEmpty()) { + // check the system property for log4j2 + log4jConfigFilePath = System.getProperty(SYS_PROP_LOG4J2_CONFIG_FILE); + } + + if (log4jConfigFilePath == null || log4jConfigFilePath.isEmpty()) { // use the default + File logConfFile = new File(AppConfig.getConfigurationsDir() + "/" + LOG_CONF_DEFAULT_XML); + if (logConfFile.exists()) { + LOG_CONF_DEFAULT = LOG_CONF_DEFAULT_XML; + } else { + LOG_CONF_DEFAULT = LOG_CONF_DEFAULT_PROPERTIES; + } + log4jConfigFilePath = Paths.get(AppConfig.getConfigurationsDir(), LOG_CONF_DEFAULT).toString(); + } + + Path p = Paths.get(log4jConfigFilePath); + File logConfFile; + if (p.isAbsolute()) { + logConfFile = Paths.get(log4jConfigFilePath).toFile(); + } else { + logConfFile = Paths.get(System.getProperty("user.dir"), log4jConfigFilePath).toFile(); + } + + String log4jConfigFileAbsolutePath = logConfFile.getAbsolutePath(); + if (!logConfFile.exists()) { + AppUtil.extractFromJar("/" + LOG_CONF_DEFAULT, logConfFile); + } + System.setProperty(SYS_PROP_LOG4J2_CONFIG_FILE, log4jConfigFileAbsolutePath); + + // Uncomment code block to check that logger is using the config file + /* + * + + logger = new Logger(AppUtil.class); + + LoggerContext loggerContext = (LoggerContext) LogManager.getContext(); + String logConfigLocation = loggerContext.getConfiguration().getConfigurationSource().getLocation(); + if (logConfigLocation == null) { + logger.error("Unable to initialize logging using log4j2 config file at " + + log4jConfigFileAbsolutePath + + ". All error messages will be logged on STDOUT."); + } else { + logger.info("Using log4j2 configuration file at location: " + logConfigLocation); + } + */ + + // From https://people.apache.org/~rgoers/log4j2/faq.html#reconfig_from_code + LoggerContext context = (LoggerContext) LogManager.getContext(false); + File file = new File(log4jConfigFileAbsolutePath); + + // this will force a reconfiguration + context.setConfigLocation(file.toURI()); + Configurator.reconfigure(); + + logger.debug("Initialized logging config"); //$NON-NLS-1$ + } + + public static void setLoggingLevel(String newLevelStr) { + if (newLevelStr == null) { + return; + } + setLoggingLevelInPropertiesFile(newLevelStr); + Level newLevel = Level.toLevel(newLevelStr); + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); + loggerConfig.setLevel(newLevel); + ctx.updateLoggers(); // This causes all Loggers to refetch information from their LoggerConfig. + } + + private static void setLoggingLevelInPropertiesFile(String newLevelStr) { + String log4j2ConfFile = System.getProperty(SYS_PROP_LOG4J2_CONFIG_FILE); + if (newLevelStr == null || log4j2ConfFile == null || !log4j2ConfFile.endsWith(".properties")) { + return; + } + Properties loggingProps = new LinkedProperties(); + try { + loggingProps.load(new FileInputStream(log4j2ConfFile)); + loggingProps.put("rootLogger.level", newLevelStr); + loggingProps.store(new FileOutputStream(log4j2ConfFile), "Logging config"); + } catch (Exception e) { + logger.error(e.getMessage()); + } + } + + public static String getLoggingConfigFile() { + return System.getProperty(SYS_PROP_LOG4J2_CONFIG_FILE); + } + + public static String getLoggingLevel() { + LoggerContext ctx = (LoggerContext) LogManager.getContext(false); + Configuration config = ctx.getConfiguration(); + LoggerConfig loggerConfig = config.getLoggerConfig(LogManager.ROOT_LOGGER_NAME); + return loggerConfig.getLevel().name(); + } + + public static String getLoggingFilePattern() { + RollingFileAppender appender = (RollingFileAppender)getAppender(RollingFileAppender.class); + if (appender == null) { + return ""; + } + return appender.getFilePattern(); + } + + public static String getLatestLoggingFile() { + RollingFileAppender appender = (RollingFileAppender)getAppender(RollingFileAppender.class); + if (appender == null) { + return ""; + } + return appender.getFileName(); + } + + private static Appender getAppender(Class appenderClass) { + LoggerContext logContext = (LoggerContext) LogManager.getContext(false); + + Map map = logContext.getConfiguration().getLoggers(); + LoggerConfig rootConfig = map.get(""); + Collection appenders = rootConfig.getAppenders().values(); + for (Appender appender : appenders) { + if (appender.getClass().getCanonicalName().equals(appenderClass.getCanonicalName())) { + return appender; + } + } + return null; + } +} diff --git a/src/main/java/com/salesforce/dataloader/util/OAuthBrowserLoginRunner.java b/src/main/java/com/salesforce/dataloader/util/OAuthBrowserLoginRunner.java new file mode 100644 index 000000000..7149aac73 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/OAuthBrowserLoginRunner.java @@ -0,0 +1,342 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.http.Header; +import org.apache.http.message.BasicNameValuePair; +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.salesforce.dataloader.client.SimplePost; +import com.salesforce.dataloader.client.SimplePostFactory; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.exception.OAuthBrowserLoginRunnerException; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.model.OAuthToken; +import com.salesforce.dataloader.ui.URLUtil; +//import com.salesforce.dataloader.ui.UIUtils; + +public class OAuthBrowserLoginRunner { + public enum LoginStatus { WAIT, FAIL, SUCCESS }; + protected static Logger logger = DLLogManager.getLogger(OAuthBrowserLoginRunner.class); + private static LoginStatus loginResult = LoginStatus.WAIT; + private String verificationURLStr = null; + String userCodeStr; + String deviceCode; + String oAuthTokenURLStr; + AppConfig appConfig; + Thread checkLoginThread; + + public OAuthBrowserLoginRunner(AppConfig appConfig, boolean skipUserCodePage) throws IOException, ParameterLoadException, OAuthBrowserLoginRunnerException { + String origEndpoint = new String(appConfig.getAuthEndpointForCurrentEnv()); + try { + startBrowserLogin(appConfig, skipUserCodePage); + } catch (Exception ex) { + logger.warn(Messages.getMessage(this.getClass(), "failedAuthStart", origEndpoint, ex.getMessage())); + if (!appConfig.isDefaultAuthEndpointForCurrentEnv(origEndpoint)) { + // retry with default endpoint URL only if user is attempting production login + retryBrowserLoginWithDefaultURL(appConfig, skipUserCodePage); + } + } finally { + // restore original value of Config.ENDPOINT property + appConfig.setAuthEndpointForCurrentEnv(origEndpoint); + } + } + + private void retryBrowserLoginWithDefaultURL(AppConfig appConfig, boolean skipUserCodePage) throws IOException, ParameterLoadException, OAuthBrowserLoginRunnerException { + logger.info(Messages.getMessage(this.getClass(), "retryAuthStart", appConfig.getDefaultAuthEndpointForCurrentEnv())); + appConfig.setAuthEndpointForCurrentEnv(appConfig.getDefaultAuthEndpointForCurrentEnv()); + startBrowserLogin(appConfig, skipUserCodePage); + } + + // Browser login uses OAuth 2.0 Device Flow - https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_device_flow.htm&type=5 + private void startBrowserLogin(AppConfig appConfig, boolean skipUserCodePage) throws IOException, ParameterLoadException, OAuthBrowserLoginRunnerException { + setLoginStatus(LoginStatus.WAIT); + this.appConfig = appConfig; + appConfig.setServerEnvironment(appConfig.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT)); + oAuthTokenURLStr = appConfig.getAuthEndpointForCurrentEnv() + "/services/oauth2/token"; + SimplePost client = SimplePostFactory.getInstance(appConfig, oAuthTokenURLStr, + new BasicNameValuePair("response_type", "device_code"), + new BasicNameValuePair(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()), + new BasicNameValuePair("scope", "api") + ); + client.post(); + InputStream in = client.getInput(); + if (!client.isSuccessful()) { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + for (int length; (length = in.read(buffer)) != -1; ) { + result.write(buffer, 0, length); + } + String response = result.toString(StandardCharsets.UTF_8.name()); + result.close(); + logger.error(response); + throw new OAuthBrowserLoginRunnerException(response); + } + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + Map responseMap = mapper.readValue(in, Map.class); + userCodeStr = (String) responseMap.get("user_code"); + deviceCode = (String)responseMap.get("device_code"); + logger.debug("User Code: " + userCodeStr); + verificationURLStr = (String) responseMap.get("verification_uri") + + "?user_code=" + userCodeStr; + logger.debug("Verification URL: " + verificationURLStr); + + // start checking for login + int pollingIntervalInSec = 5; + try { + pollingIntervalInSec = ((Integer)responseMap.get("interval")).intValue(); + } catch (NumberFormatException e) { + // fail silently + } + checkLoginThread = startLoginCheck(pollingIntervalInSec); + + if (!skipUserCodePage) { + return; + } + + // try to skip the page with pre-filled user code. + client = SimplePostFactory.getInstance(appConfig, verificationURLStr, + new BasicNameValuePair("", "") + ); + client.post(); + in = client.getInput(); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + for (int length; (length = in.read(buffer)) != -1; ) { + result.write(buffer, 0, length); + } + + String response = result.toString(StandardCharsets.UTF_8.name()); + if (!client.isSuccessful()) { + // did not succeed in skipping the page with pre-filled user code, show it + logger.error(response); + URLUtil.openURL(verificationURLStr); + } + + List nvPairList = parseTokenPageHTML(response); + + client = SimplePostFactory.getInstance(appConfig, responseMap.get("verification_uri").toString(), + new BasicNameValuePair("save", "Connect") + ); + for (BasicNameValuePair pair : nvPairList) { + client.addBasicNameValuePair(pair); + } + client.post(); + in = client.getInput(); + result = new ByteArrayOutputStream(); + buffer = new byte[1024]; + for (int length; (length = in.read(buffer)) != -1; ) { + result.write(buffer, 0, length); + } + if (client.getStatusCode() == 302) { + Header[] locationHeaders = client.getResponseHeaders("Location"); + String redirectURL = locationHeaders[0].getValue(); + URLUtil.openURL(redirectURL); + } else { + // did not succeed in skipping the page with pre-filled user code, show it + URLUtil.openURL(verificationURLStr); + } + + } + + private Thread startLoginCheck(final int pollingIntervalInSec) { + Thread successfulLogincheckerThread = new Thread() { + public void run() { + // Poll for 20 minutes. + // Expiry of user code is detected by server returning an error + // other than 'authorization_pending'. + int maxPollingTimeInSec = 1200; + int elapsedTimeInSec = 0; + SimplePost client; + InputStream in; + while (elapsedTimeInSec <= maxPollingTimeInSec) { + try { + Thread.sleep(pollingIntervalInSec * 1000); + } catch (InterruptedException e) { + // do nothing + } + elapsedTimeInSec += pollingIntervalInSec; + client = SimplePostFactory.getInstance(appConfig, oAuthTokenURLStr, + new BasicNameValuePair("grant_type", "device"), + new BasicNameValuePair(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()), + new BasicNameValuePair("code", deviceCode) + ); + try { + client.post(); + } catch (ParameterLoadException e) { + logger.error(e.getMessage());; + setLoginStatus(LoginStatus.FAIL); + return; + } catch (IOException e) { + logger.error(e.getMessage()); + setLoginStatus(LoginStatus.FAIL); + return; + } + in = client.getInput(); + if (client.isSuccessful()) { + try { + processSuccessfulLogin(client.getInput(), appConfig); + } catch (IOException e) { + logger.error(e.getMessage()); + setLoginStatus(LoginStatus.FAIL); + return; + } + // got the session id => SUCCESSful login + setLoginStatus(LoginStatus.SUCCESS); + return; + } else { // read the error message and log it + ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true); + try { + Map responseMap = mapper.readValue(in, Map.class); + String errorStr = (String)responseMap.get("error"); + String errorDesc = (String)responseMap.get("error_description"); + if ("authorization_pending".equalsIgnoreCase(errorStr)) { + // waiting for the user to login + logger.debug(errorStr + " - " + errorDesc); + } else { + // a failure occurred. Exit. + logger.error(errorStr + " - " + errorDesc); + setLoginStatus(LoginStatus.FAIL); + break; + } + } catch (IOException e) { + logger.debug(e.getMessage()); + continue; + } + } + } // while loop + logger.error("User closed the dialog or timed out waiting for login"); + setLoginStatus(LoginStatus.FAIL); + } + }; + successfulLogincheckerThread.start(); + return successfulLogincheckerThread; + } + + private synchronized void setLoginStatus(LoginStatus value) { + loginResult = value; + } + + public LoginStatus getLoginStatus() { + return loginResult; + } + + public String getUserCode() { + return userCodeStr; + } + + public String getDeviceCode() { + return deviceCode; + } + + public String getVerificationURLStr() { + return verificationURLStr; + } + + public String getOAuthTokenURL() { + return oAuthTokenURLStr; + } + + public boolean isLoginProcessCompleted() { + return !this.checkLoginThread.isAlive(); + } + + private List parseTokenPageHTML(String response) { + String id, value; + List nvPairList = new ArrayList(); + + Pattern pattern = Pattern.compile("<(?!!)(?!/)\\s*([a-zA-Z0-9]+)(.*?)>"); + Matcher matcher = pattern.matcher(response); + while (matcher.find()) { + String tagName = matcher.group(1); + String attributes = matcher.group(2); + if (!tagName.equalsIgnoreCase("input")) { + continue; + } + Pattern attributePattern = Pattern.compile("(\\S+)=['\"]{1}([^>]*?)['\"]{1}"); + Matcher attributeMatcher = attributePattern.matcher(attributes); + id = ""; + value = ""; + while(attributeMatcher.find()) { + String attributeName = attributeMatcher.group(1); + String attributeValue = attributeMatcher.group(2); + if (attributeName.equalsIgnoreCase("id")) { + id = attributeValue; + } + if (attributeName.equalsIgnoreCase("value")) { + value = attributeValue; + } + } + if (!id.equals("")) { + nvPairList.add(new BasicNameValuePair(id, value)); + } + } + + return nvPairList; + } + + + public static void processSuccessfulLogin(InputStream httpResponseInputStream, AppConfig appConfig) throws IOException { + + StringBuilder builder = new StringBuilder(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(httpResponseInputStream, StandardCharsets.UTF_8.name())); + for (int c = bufferedReader.read(); c != -1; c = bufferedReader.read()) { + builder.append((char) c); + } + + String jsonTokenResult = builder.toString(); + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + OAuthToken token = gson.fromJson(jsonTokenResult, OAuthToken.class); + appConfig.setAuthEndpointForCurrentEnv(token.getInstanceUrl()); + appConfig.setValue(AppConfig.PROP_OAUTH_ACCESSTOKEN, token.getAccessToken()); + appConfig.setValue(AppConfig.PROP_OAUTH_REFRESHTOKEN, token.getRefreshToken()); + appConfig.setValue(AppConfig.PROP_OAUTH_INSTANCE_URL, token.getInstanceUrl()); + } +} diff --git a/src/main/java/com/salesforce/dataloader/util/OrderedProperties.java b/src/main/java/com/salesforce/dataloader/util/OrderedProperties.java new file mode 100644 index 000000000..46f0808e8 --- /dev/null +++ b/src/main/java/com/salesforce/dataloader/util/OrderedProperties.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +/** + * Based on + * View Source + * + */ +public class OrderedProperties extends Properties { + + private static final long serialVersionUID = 1L; + LinkedHashMap props; + + public OrderedProperties() { + super (); + + props = new LinkedHashMap(); + } + + public Object put(Object key, Object value) { + if (props.containsKey(key)) { + props.remove(key); + } + + props.put(key, value); + + return super .put(key, value); + } + + public Set> entrySet() { + return props.entrySet(); + } + + public Object remove(Object key) { + props.remove(key); + + return super .remove(key); + } +} \ No newline at end of file diff --git a/src/main/java/com/salesforce/dataloader/util/StreamGobbler.java b/src/main/java/com/salesforce/dataloader/util/StreamGobbler.java index 878ad7441..b0f7999e8 100644 --- a/src/main/java/com/salesforce/dataloader/util/StreamGobbler.java +++ b/src/main/java/com/salesforce/dataloader/util/StreamGobbler.java @@ -26,8 +26,6 @@ package com.salesforce.dataloader.util; -import org.apache.log4j.Logger; - import java.io.*; /** diff --git a/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java b/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java new file mode 100644 index 000000000..1435cbabb --- /dev/null +++ b/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package org.apache.logging.log4j.core.lookup; + + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.config.plugins.Plugin; + + +/** + * Looks up keys from JNDI resources. + */ +@Plugin(name = "jndi", category = StrLookup.CATEGORY) +public class JndiLookup extends AbstractLookup { + + /** + * Looks up the value of the JNDI resource. + * @param event The current LogEvent (is ignored by this StrLookup). + * @param key the JNDI resource name to be looked up, may be null + * @return The String value of the JNDI resource. + */ + @Override + public String lookup(final LogEvent event, final String key) { + return null; + } +} diff --git a/src/main/nsis/bin/encrypt.bat b/src/main/nsis/bin/encrypt.bat deleted file mode 100644 index 2da382f45..000000000 --- a/src/main/nsis/bin/encrypt.bat +++ /dev/null @@ -1,23 +0,0 @@ -@echo off - -IF "%JAVA_HOME%" == "" ( - for /f "tokens=*" %%i in ('${pom.build.finalName}-java-home.exe') do ( - IF EXIST "%%i" ( - set JAVA_HOME=%%i - ) ELSE ( - echo %%i - ) - ) -) - -IF "%JAVA_HOME%" == "" ( - echo To run encrypt.bat, set the JAVA_HOME environment variable to the directory where the Java Runtime Environment ^(JRE^) is installed. -) ELSE ( - IF NOT EXIST "%JAVA_HOME%" ( - echo We couldn't find the Java Runtime Environment ^(JRE^) in directory "%JAVA_HOME%". To run process.bat, set the JAVA_HOME environment variable to the directory where the JRE is installed. - ) ELSE ( - "%JAVA_HOME%\bin\java" -cp ..\${pom.build.finalName}-uber.jar com.salesforce.dataloader.security.EncryptionUtil %* - ) -) - - diff --git a/src/main/nsis/bin/process.bat b/src/main/nsis/bin/process.bat deleted file mode 100644 index 2af6592bf..000000000 --- a/src/main/nsis/bin/process.bat +++ /dev/null @@ -1,48 +0,0 @@ -@echo off -if not [%1]==[] goto run -echo. -echo Usage: process ^ ^[process name^] -echo. -echo configuration directory -- directory that contains configuration files, -echo i.e. config.properties, process-conf.xml, database-conf.xml -echo. -echo process name -- optional name of a batch process bean in process-conf.xml, -echo for example: -echo. -echo process ../myconfigdir AccountInsert -echo. -echo If process name is not specified, the parameter values from config.properties -echo will be used to run the process instead of process-conf.xml, -echo for example: -echo. -echo process ../myconfigdir -echo. - -goto end - -:run -set PROCESS_OPTION= -if not [%2]==[] set PROCESS_OPTION=process.name=%2 - - -IF "%JAVA_HOME%" == "" ( - for /f "tokens=*" %%i in ('${pom.build.finalName}-java-home.exe') do ( - IF EXIST "%%i" ( - set JAVA_HOME=%%i - ) ELSE ( - echo %%i - ) - ) -) - -IF "%JAVA_HOME%" == "" ( - echo To run process.bat, set the JAVA_HOME environment variable to the directory where the Java Runtime Environment ^(JRE^) is installed. -) ELSE ( - IF NOT EXIST "%JAVA_HOME%" ( - echo We couldn't find the Java Runtime Environment ^(JRE^) in directory "%JAVA_HOME%". To run process.bat, set the JAVA_HOME environment variable to the directory where the JRE is installed. - ) ELSE ( - "%JAVA_HOME%\bin\java" -cp ..\${pom.build.finalName}-uber.jar -Dsalesforce.config.dir=%1 com.salesforce.dataloader.process.ProcessRunner %PROCESS_OPTION% - ) -) - -:end diff --git a/src/main/nsis/config.properties b/src/main/nsis/config.properties deleted file mode 100644 index a5edb9ff1..000000000 --- a/src/main/nsis/config.properties +++ /dev/null @@ -1,5 +0,0 @@ -#Loader Config -#Thu Sep 10 09:37:47 PDT 2009 -sfdc.endpoint=https\://login.salesforce.com -loader.hideWelcome=true -#placeholder \ No newline at end of file diff --git a/src/main/nsis/installer-salesforce-dataLoader.png b/src/main/nsis/installer-salesforce-dataLoader.png deleted file mode 100644 index 4a02e42d8..000000000 Binary files a/src/main/nsis/installer-salesforce-dataLoader.png and /dev/null differ diff --git a/src/main/nsis/license.rtf b/src/main/nsis/license.rtf deleted file mode 100644 index 619fcfd23..000000000 Binary files a/src/main/nsis/license.rtf and /dev/null differ diff --git a/src/main/nsis/licenses/ApacheCommonsBeanutils_license.txt b/src/main/nsis/licenses/ApacheCommonsBeanutils_license.txt deleted file mode 100644 index de6706f26..000000000 --- a/src/main/nsis/licenses/ApacheCommonsBeanutils_license.txt +++ /dev/null @@ -1,58 +0,0 @@ -/* ==================================================================== - * The Apache Software License, Version 1.1 - * - * Copyright (c) 2000 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, - * if any, must include the following acknowledgment: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowledgment may appear in the software itself, - * if and wherever such third-party acknowledgments normally appear. - * - * 4. The names "Apache" and "Apache Software Foundation" must - * not be used to endorse or promote products derived from this - * software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache", - * nor may "Apache" appear in their name, without prior written - * permission of the Apache Software Foundation. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation. For more - * information on the Apache Software Foundation, please see - * . - * - * Portions of this software are based upon public domain software - * originally written at the National Center for Supercomputing Applications, - * University of Illinois, Urbana-Champaign. - */ - diff --git a/src/main/nsis/licenses/ApacheCommonsCollections_license.txt b/src/main/nsis/licenses/ApacheCommonsCollections_license.txt deleted file mode 100644 index 4088a92d0..000000000 --- a/src/main/nsis/licenses/ApacheCommonsCollections_license.txt +++ /dev/null @@ -1,203 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. - - \ No newline at end of file diff --git a/src/main/nsis/licenses/ApacheCommonsDatabaseConnectionPool_license.txt b/src/main/nsis/licenses/ApacheCommonsDatabaseConnectionPool_license.txt deleted file mode 100644 index d64569567..000000000 --- a/src/main/nsis/licenses/ApacheCommonsDatabaseConnectionPool_license.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/src/main/nsis/licenses/ApacheCommonsLogging_license.txt b/src/main/nsis/licenses/ApacheCommonsLogging_license.txt deleted file mode 100644 index b5cd7221b..000000000 --- a/src/main/nsis/licenses/ApacheCommonsLogging_license.txt +++ /dev/null @@ -1,203 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. - - diff --git a/src/main/nsis/licenses/ApacheCommonsObjectPool_license.txt b/src/main/nsis/licenses/ApacheCommonsObjectPool_license.txt deleted file mode 100644 index d64569567..000000000 --- a/src/main/nsis/licenses/ApacheCommonsObjectPool_license.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/src/main/nsis/licenses/ApacheLog4J_license.txt b/src/main/nsis/licenses/ApacheLog4J_license.txt deleted file mode 100644 index 261eeb9e9..000000000 --- a/src/main/nsis/licenses/ApacheLog4J_license.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/src/main/nsis/licenses/EclipseSwt_license.txt b/src/main/nsis/licenses/EclipseSwt_license.txt deleted file mode 100644 index 16cc69a52..000000000 --- a/src/main/nsis/licenses/EclipseSwt_license.txt +++ /dev/null @@ -1,87 +0,0 @@ -Eclipse Public License - v 1.0 - -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. - -1. DEFINITIONS - -"Contribution" means: - -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and - -b) in the case of each subsequent Contributor: - -i) changes to the Program, and - -ii) additions to the Program; - -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. - -"Contributor" means any person or entity that distributes the Program. - -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. - -"Program" means the Contributions distributed in accordance with this Agreement. - -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. - -2. GRANT OF RIGHTS - -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. - -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. - -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. - -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. - -3. REQUIREMENTS - -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: - -a) it complies with the terms and conditions of this Agreement; and - -b) its license agreement: - -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; - -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; - -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and - -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. - -When the Program is made available in source code form: - -a) it must be made available under this Agreement; and - -b) a copy of this Agreement must be included with each copy of the Program. - -Contributors may not remove or alter any copyright notices contained within the Program. - -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. - -4. COMMERCIAL DISTRIBUTION - -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. - -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. - -5. NO WARRANTY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. - -6. DISCLAIMER OF LIABILITY - -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - -7. GENERAL - -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. - -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. - -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. - -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. - -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. diff --git a/src/main/nsis/licenses/OSGI_license.txt b/src/main/nsis/licenses/OSGI_license.txt deleted file mode 100644 index 62cda3b11..000000000 --- a/src/main/nsis/licenses/OSGI_license.txt +++ /dev/null @@ -1,64 +0,0 @@ -Copyright (c) 2000, 2003 - -The Open Services Gateway Initiative -Bishop Ranch 2 -2694 Bishop Drive -Suite 275 -San Ramon -CA 94583 USA - -All Rights Reserved. - -LEGAL TERMS AND CONDITIONS REGARDING SPECIFICATION - -Implementation of certain elements of the Open Services Gateway -Initiative (OSGi) Specification may be subject to third party -intellectual property rights, including without limitation, patent -rights (such a third party may or may not be a member of OSGi). OSGi is -not responsible and shall not be held responsible in any manner for -identifying or failing to identify any or all such third party -intellectual property rights. - -THE RECIPIENT ACKNOWLEDGES AND AGREES THAT THE SPECIFICATION IS PROVIDED -"AS IS" AND WITH NO WARRANTIES WHATSOEVER, WHETHER EXPRESS, IMPLIED OR -STATUTORY, INCLUDING, BUT NOT LIMITED TO ANY WARRANTY OF -MERCHANTABILITY, NONINFRINGEMENT, FITNESS OF ANY PARTICULAR PURPOSE, OR -ANY WARRANTY OTHERWISE ARISING OUT OF ANY PROPOSAL, SPECIFICATION, OR -SAMPLE. THE RECIPIENT'S USE OF THE SPECIFICATION IS SOLELY AT THE -RECIPIENT'S OWN RISK. THE RECIPIENT'S USE OF THE SPECIFICATION IS -SUBJECT TO THE RECIPIENT'S OSGi MEMBER AGREEMENT, IN THE EVENT THAT THE -RECIPIENT IS AN OSGi MEMBER. IN NO EVENT SHALL OSGi BE LIABLE OR -OBLIGATED TO THE RECIPIENT OR ANY THIRD PARTY IN ANY MANNER FOR ANY -SPECIAL, NON-COMPENSATORY, CONSEQUENTIAL, INDIRECT, INCIDENTAL, -STATUTORY OR PUNITIVE DAMAGES OF ANY KIND, INCLUDING, WITHOUT -LIMITATION, LOST PROFITS AND LOST REVENUE, REGARDLESS OF THE FORM OF -ACTION, WHETHER IN CONTRACT, TORT, NEGLIGENCE, STRICT PRODUCT LIABILITY, -OR OTHERWISE, EVEN IF OSGi HAS BEEN INFORMED OF OR IS AWARE OF THE -POSSIBILITY OF ANY SUCH DAMAGES IN ADVANCE. - -THE LIMITATIONS SET FORTH ABOVE SHALL BE DEEMED TO APPLY TO THE MAXIMUM -EXTENT PERMITTED BY APPLICABLE LAW AND NOTWITHSTANDING THE FAILURE OF -THE ESSENTIAL PURPOSE OF ANY LIMITED REMEDIES AVAILABLE TO THE -RECIPIENT. THE RECIPIENT ACKNOWLEDGES AND AGREES THAT THE RECIPIENT HAS -FULLY CONSIDERED THE FOREGOING ALLOCATION OF RISK AND FINDS IT -REASONABLE, AND THAT THE FOREGOING LIMITATIONS ARE AN ESSENTIAL BASIS OF -THE BARGAIN BETWEEN THE RECIPIENT AND OSGi. IF THE RECIPIENT USES THE -SPECIFICATION, THE RECIPIENT AGREES TO ALL OF THE FOREGOING TERMS AND -CONDITIONS. IF THE RECIPIENT DOES NOT AGREE TO THESE TERMS AND -CONDITIONS, THE RECIPIENT SHOULD NOT USE THE SPECIFICATION AND SHOULD -CONTACT OSGi IMMEDIATELY. - -Trademarks - -OSGi(TM) is a trademark, registered trademark, or service mark of The -Open Services Gateway Initiative in the US and other countries. Java is -a trademark, registered trademark, or service mark of Sun Microsystems, -Inc. in the US and other countries. All other trademarks, registered -trademarks, or service marks used in this document are the property of -their respective owners and are hereby recognized. - -Feedback - -This specification can be downloaded from the OSGi web site: -http://www.osgi.org. Comments about this specification can be mailed -to: speccomments@mail.osgi.org diff --git a/src/main/nsis/licenses/OpenSymphonyQuartzJobScheduler_license.txt b/src/main/nsis/licenses/OpenSymphonyQuartzJobScheduler_license.txt deleted file mode 100644 index d64569567..000000000 --- a/src/main/nsis/licenses/OpenSymphonyQuartzJobScheduler_license.txt +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/src/main/nsis/licenses/OracleJdbc_license.txt b/src/main/nsis/licenses/OracleJdbc_license.txt deleted file mode 100644 index fabaeb63c..000000000 --- a/src/main/nsis/licenses/OracleJdbc_license.txt +++ /dev/null @@ -1,103 +0,0 @@ -Oracle Technology Network Development and Distribution License Terms - - -Export Controls on the Programs -Selecting the "Accept License Agreement" button is a confirmation of your agreement that you comply, now and during the trial term, with each of the following statements: - - --You are not a citizen, national, or resident of, and are not under control of, the government of Cuba, Iran, Sudan, Libya, North Korea, Syria, nor any country to which the United States has prohibited export. --You will not download or otherwise export or re-export the Programs, directly or indirectly, to the above mentioned countries nor to citizens, nationals or residents of those countries. --You are not listed on the United States Department of Treasury lists of Specially Designated Nationals, Specially Designated Terrorists, and Specially Designated Narcotic Traffickers, nor are you listed on the United States Department of Commerce Table of Denial Orders. - - -You will not download or otherwise export or re-export the Programs, directly or indirectly, to persons on the above mentioned lists. - - -You will not use the Programs for, and will not allow the Programs to be used for, any purposes prohibited by United States law, including, without limitation, for the development, design, manufacture or production of nuclear, chemical or biological weapons of mass destruction. - - -EXPORT RESTRICTIONS -You agree that U.S. export control laws and other applicable export and import laws govern your use of the programs, including technical data; additional information can be found on Oracle®'s Global Trade Compliance web site (http://www.oracle.com/products/export). - - -You agree that neither the programs nor any direct product thereof will be exported, directly, or indirectly, in violation of these laws, or will be used for any purpose prohibited by these laws including, without limitation, nuclear, chemical, or biological weapons proliferation. - - -Oracle Employees: Under no circumstances are Oracle Employees authorized to download software for the purpose of distributing it to customers. Oracle products are available to employees for internal use or demonstration purposes only. In keeping with Oracle's trade compliance obligations under U.S. and applicable multilateral law, failure to comply with this policy could result in disciplinary action up to and including termination. - - -Note: You are bound by the Oracle Technology Network ("OTN") License Agreement terms. The OTN License Agreement terms also apply to all updates you receive under your Technology Track subscription. - - -The OTN License Agreement terms below supercede any shrinkwrap license on the OTN Technology Track software CDs and previous OTN License terms (including the Oracle Program License as modified by the OTN Program Use Certificate). - - -Oracle Technology Network Development and Distribution License Agreement - -"We," "us," and "our" refers to Oracle America, Inc., for and on behalf of itself and its subsidiaries and affiliates under common control. "You" and "your" refers to the individual or entity that wishes to use the programs from Oracle. "Programs" refers to the software product you wish to download and use and program documentation. "License" refers to your right to use the programs under the terms of this agreement. This agreement is governed by the substantive and procedural laws of California. You and Oracle agree to submit to the exclusive jurisdiction of, and venue in, the courts of San Francisco, San Mateo, or Santa Clara counties in California in any dispute arising out of or relating to this agreement. - -We are willing to license the programs to you only upon the condition that you accept all of the terms contained in this agreement. Read the terms carefully and select the "Accept" button at the bottom of the page to confirm your acceptance. If you are not willing to be bound by these terms, select the "Do Not Accept" button and the registration process will not continue. - -License Rights -We grant you a nonexclusive, nontransferable limited license to use the programs: (a) for purposes of developing, testing, prototyping and running applications you have developed for your own internal data processing operations; (b) to distribute the programs with applications you have developed to your customers provided that each such licensee agrees to license terms consistent with the terms of this Agreement, you do not charge your end users any additional fees for the use of the programs, and your end users may only use the programs to run your applications for their own business operations; and (c) to use the programs to provide third party demonstrations and training. You are not permitted to use the programs for any purpose other than as permitted under this Agreement. If you want to use the programs for any purpose other than as expressly permitted under this agreement you must contact us, or an Oracle reseller, to obtain the appropriate license. We may audit your use and distribution of the programs. Program documentation is either shipped with the programs, or documentation may accessed online at http://www.oracle.com/technetwork/indexes/documentation/index.html. - -Ownership and Restrictions -We retain all ownership and intellectual property rights in the programs. You may make a sufficient number of copies of the programs for the licensed use and one copy of the programs for backup purposes. - -You may not: -- use the programs for any purpose other than as provided above; -- distribute the programs unless accompanied with your applications; -- charge your end users for use of the programs; -- remove or modify any program markings or any notice of our proprietary rights; -- use the programs to provide third party training on the content and/or functionality of the programs, except for training your licensed users; -- assign this agreement or give the programs, program access or an interest in the programs to any individual or entity except as provided under this agreement; -- cause or permit reverse engineering (unless required by law for interoperability), disassembly or decompilation of the programs; -- disclose results of any program benchmark tests without our prior consent. - -Program Distribution -We grant you a nonexclusive, nontransferable right to copy and distribute the programs to your end users provided that you do not charge your end users for use of the programs and provided your end users may only use the programs to run your applications for their business operations. Prior to distributing the programs you shall require your end users to execute an agreement binding them to terms consistent with those contained in this section and the sections of this agreement entitled "License Rights," "Ownership and Restrictions," "Export," "Disclaimer of Warranties and Exclusive Remedies," "No Technical Support," "End of Agreement," "Relationship Between the Parties," and "Open Source." You must also include a provision stating that your end users shall have no right to distribute the programs, and a provision specifying us as a third party beneficiary of the agreement. You are responsible for obtaining these agreements with your end users. - -You agree to: (a) defend and indemnify us against all claims and damages caused by your distribution of the programs in breach of this agreements and/or failure to include the required contractual provisions in your end user agreement as stated above; (b) keep executed end user agreements and records of end user information including name, address, date of distribution and identity of programs distributed; (c) allow us to inspect your end user agreements and records upon request; and, (d) enforce the terms of your end user agreements so as to effect a timely cure of any end user breach, and to notify us of any breach of the terms. - -Export -You agree that U.S. export control laws and other applicable export and import laws govern your use of the programs, including technical data; additional information can be found on Oracle's Global Trade Compliance web site located at http://www.oracle.com/products/export/index.html?content.html. You agree that neither the programs nor any direct product thereof will be exported, directly, or indirectly, in violation of these laws, or will be used for any purpose prohibited by these laws including, without limitation, nuclear, chemical, or biological weapons proliferation. - -Disclaimer of Warranty and Exclusive Remedies - -THE PROGRAMS ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND. WE FURTHER DISCLAIM ALL WARRANTIES, EXPRESS AND IMPLIED, INCLUDING WITHOUT LIMITATION, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NONINFRINGEMENT. - -IN NO EVENT SHALL WE BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, PUNITIVE OR CONSEQUENTIAL DAMAGES, OR DAMAGES FOR LOSS OF PROFITS, REVENUE, DATA OR DATA USE, INCURRED BY YOU OR ANY THIRD PARTY, WHETHER IN AN ACTION IN CONTRACT OR TORT, EVEN IF WE HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. OUR ENTIRE LIABILITY FOR DAMAGES HEREUNDER SHALL IN NO EVENT EXCEED ONE THOUSAND DOLLARS (U.S. $1,000). - -No Technical Support -Our technical support organization will not provide technical support, phone support, or updates to you for the programs licensed under this agreement. - -Restricted Rights -If you distribute a license to the United States government, the programs, including documentation, shall be considered commercial computer software and you will place a legend, in addition to applicable copyright notices, on the documentation, and on the media label, substantially similar to the following: - -NOTICE OF RESTRICTED RIGHTS - -"Programs delivered subject to the DOD FAR Supplement are 'commercial computer software' and use, duplication, and disclosure of the programs, including documentation, shall be subject to the licensing restrictions set forth in the applicable Oracle license agreement. Otherwise, programs delivered subject to the Federal Acquisition Regulations are 'restricted computer software' and use, duplication, and disclosure of the programs, including documentation, shall be subject to the restrictions in FAR 52.227-19, Commercial Computer Software-Restricted Rights (June 1987). Oracle America, Inc., 500 Oracle Parkway, Redwood City, CA 94065." - -End of Agreement -You may terminate this agreement by destroying all copies of the programs. We have the right to terminate your right to use the programs if you fail to comply with any of the terms of this agreement, in which case you shall destroy all copies of the programs. - -Relationship Between the Parties -The relationship between you and us is that of licensee/licensor. Neither party will represent that it has any authority to assume or create any obligation, express or implied, on behalf of the other party, nor to represent the other party as agent, employee, franchisee, or in any other capacity. Nothing in this agreement shall be construed to limit either party's right to independently develop or distribute software that is functionally similar to the other party's products, so long as proprietary information of the other party is not included in such software. - -Open Source -"Open Source" software - software available without charge for use, modification and distribution - is often licensed under terms that require the user to make the user's modifications to the Open Source software or any software that the user 'combines' with the Open Source software freely available in source code form. If you use Open Source software in conjunction with the programs, you must ensure that your use does not: (i) create, or purport to create, obligations of us with respect to the Oracle programs; or (ii) grant, or purport to grant, to any third party any rights to or immunities under our intellectual property or proprietary rights in the Oracle programs. For example, you may not develop a software program using an Oracle program and an Open Source program where such use results in a program file(s) that contains code from both the Oracle program and the Open Source program (including without limitation libraries) if the Open Source program is licensed under a license that requires any "modifications" be made freely available. You also may not combine the Oracle program with programs licensed under the GNU General Public License ("GPL") in any manner that could cause, or could be interpreted or asserted to cause, the Oracle program or any modifications thereto to become subject to the terms of the GPL. - -Entire Agreement -You agree that this agreement is the complete agreement for the programs and licenses, and this agreement supersedes all prior or contemporaneous agreements or representations. If any term of this agreement is found to be invalid or unenforceable, the remaining provisions will remain effective. - -Last updated: 01/24/09 - - - -Should you have any questions concerning this License Agreement, or if you desire to contact Oracle for any reason, please write: -Oracle America, Inc. -500 Oracle Parkway, -Redwood City, CA 94065 - - -Oracle may contact you to ask if you had a satisfactory experience installing and using this OTN software download. diff --git a/src/main/nsis/licenses/SpringFramework_license.txt b/src/main/nsis/licenses/SpringFramework_license.txt deleted file mode 100644 index 261eeb9e9..000000000 --- a/src/main/nsis/licenses/SpringFramework_license.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. diff --git a/src/main/nsis/licenses/wsc-license.rtf b/src/main/nsis/licenses/wsc-license.rtf deleted file mode 100644 index 4e351c468..000000000 --- a/src/main/nsis/licenses/wsc-license.rtf +++ /dev/null @@ -1,203 +0,0 @@ -{\rtf1\adeflang1025\ansi\ansicpg1252\uc1\adeff31507\deff0\stshfdbch0\stshfloch31506\stshfhich31506\stshfbi31506\deflang1033\deflangfe1033\themelang1033\themelangfe0\themelangcs0{\fonttbl{\f0\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f2\fbidi \fmodern\fcharset0\fprq1{\*\panose 02070309020205020404}Courier New;} -{\f3\fbidi \froman\fcharset2\fprq2{\*\panose 05050102010706020507}Symbol;}{\f10\fbidi \fnil\fcharset2\fprq2{\*\panose 05000000000000000000}Wingdings;}{\f34\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria Math;} -{\f37\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;}{\flomajor\f31500\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbmajor\f31501\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhimajor\f31502\fbidi \froman\fcharset0\fprq2{\*\panose 02040503050406030204}Cambria;} -{\fbimajor\f31503\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\flominor\f31504\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;} -{\fdbminor\f31505\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\fhiminor\f31506\fbidi \fswiss\fcharset0\fprq2{\*\panose 020f0502020204030204}Calibri;} -{\fbiminor\f31507\fbidi \froman\fcharset0\fprq2{\*\panose 02020603050405020304}Times New Roman;}{\f39\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\f40\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\f42\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\f43\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\f44\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\f45\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\f46\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\f47\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\f59\fbidi \fmodern\fcharset238\fprq1 Courier New CE;}{\f60\fbidi \fmodern\fcharset204\fprq1 Courier New Cyr;} -{\f62\fbidi \fmodern\fcharset161\fprq1 Courier New Greek;}{\f63\fbidi \fmodern\fcharset162\fprq1 Courier New Tur;}{\f64\fbidi \fmodern\fcharset177\fprq1 Courier New (Hebrew);}{\f65\fbidi \fmodern\fcharset178\fprq1 Courier New (Arabic);} -{\f66\fbidi \fmodern\fcharset186\fprq1 Courier New Baltic;}{\f67\fbidi \fmodern\fcharset163\fprq1 Courier New (Vietnamese);}{\f379\fbidi \froman\fcharset238\fprq2 Cambria Math CE;}{\f380\fbidi \froman\fcharset204\fprq2 Cambria Math Cyr;} -{\f382\fbidi \froman\fcharset161\fprq2 Cambria Math Greek;}{\f383\fbidi \froman\fcharset162\fprq2 Cambria Math Tur;}{\f386\fbidi \froman\fcharset186\fprq2 Cambria Math Baltic;}{\f409\fbidi \fswiss\fcharset238\fprq2 Calibri CE;} -{\f410\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\f412\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\f413\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;}{\f416\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;} -{\flomajor\f31508\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\flomajor\f31509\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flomajor\f31511\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\flomajor\f31512\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\flomajor\f31513\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flomajor\f31514\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\flomajor\f31515\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\flomajor\f31516\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbmajor\f31518\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\fdbmajor\f31519\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fdbmajor\f31521\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbmajor\f31522\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\fdbmajor\f31523\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fdbmajor\f31524\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbmajor\f31525\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\fdbmajor\f31526\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fhimajor\f31528\fbidi \froman\fcharset238\fprq2 Cambria CE;}{\fhimajor\f31529\fbidi \froman\fcharset204\fprq2 Cambria Cyr;} -{\fhimajor\f31531\fbidi \froman\fcharset161\fprq2 Cambria Greek;}{\fhimajor\f31532\fbidi \froman\fcharset162\fprq2 Cambria Tur;}{\fhimajor\f31535\fbidi \froman\fcharset186\fprq2 Cambria Baltic;} -{\fbimajor\f31538\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbimajor\f31539\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\fbimajor\f31541\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;} -{\fbimajor\f31542\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbimajor\f31543\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\fbimajor\f31544\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);} -{\fbimajor\f31545\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbimajor\f31546\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\flominor\f31548\fbidi \froman\fcharset238\fprq2 Times New Roman CE;} -{\flominor\f31549\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;}{\flominor\f31551\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\flominor\f31552\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;} -{\flominor\f31553\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);}{\flominor\f31554\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\flominor\f31555\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;} -{\flominor\f31556\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}{\fdbminor\f31558\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fdbminor\f31559\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fdbminor\f31561\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fdbminor\f31562\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fdbminor\f31563\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fdbminor\f31564\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fdbminor\f31565\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fdbminor\f31566\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);} -{\fhiminor\f31568\fbidi \fswiss\fcharset238\fprq2 Calibri CE;}{\fhiminor\f31569\fbidi \fswiss\fcharset204\fprq2 Calibri Cyr;}{\fhiminor\f31571\fbidi \fswiss\fcharset161\fprq2 Calibri Greek;}{\fhiminor\f31572\fbidi \fswiss\fcharset162\fprq2 Calibri Tur;} -{\fhiminor\f31575\fbidi \fswiss\fcharset186\fprq2 Calibri Baltic;}{\fbiminor\f31578\fbidi \froman\fcharset238\fprq2 Times New Roman CE;}{\fbiminor\f31579\fbidi \froman\fcharset204\fprq2 Times New Roman Cyr;} -{\fbiminor\f31581\fbidi \froman\fcharset161\fprq2 Times New Roman Greek;}{\fbiminor\f31582\fbidi \froman\fcharset162\fprq2 Times New Roman Tur;}{\fbiminor\f31583\fbidi \froman\fcharset177\fprq2 Times New Roman (Hebrew);} -{\fbiminor\f31584\fbidi \froman\fcharset178\fprq2 Times New Roman (Arabic);}{\fbiminor\f31585\fbidi \froman\fcharset186\fprq2 Times New Roman Baltic;}{\fbiminor\f31586\fbidi \froman\fcharset163\fprq2 Times New Roman (Vietnamese);}} -{\colortbl;\red0\green0\blue0;\red0\green0\blue255;\red0\green255\blue255;\red0\green255\blue0;\red255\green0\blue255;\red255\green0\blue0;\red255\green255\blue0;\red255\green255\blue255;\red0\green0\blue128;\red0\green128\blue128;\red0\green128\blue0; -\red128\green0\blue128;\red128\green0\blue0;\red128\green128\blue0;\red128\green128\blue128;\red192\green192\blue192;}{\*\defchp \f31506\fs22 }{\*\defpap \ql \li0\ri0\sa200\sl276\slmult1 -\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 }\noqfpromote {\stylesheet{\ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31507\afs22\alang1025 -\ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext0 \sqformat \spriority0 \styrsid15482456 Normal;}{\*\cs10 \additive \ssemihidden \sunhideused \spriority1 Default Paragraph Font;}{\* -\ts11\tsrowd\trftsWidthB3\trpaddl108\trpaddr108\trpaddfl3\trpaddft3\trpaddfb3\trpaddfr3\trcbpat1\trcfpat1\tblind0\tblindtype3\tscellwidthfts0\tsvertalt\tsbrdrt\tsbrdrl\tsbrdrb\tsbrdrr\tsbrdrdgl\tsbrdrdgr\tsbrdrh\tsbrdrv \ql \li0\ri0\sa200\sl276\slmult1 -\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af31506\afs22\alang1025 \ltrch\fcs0 \f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 \snext11 \ssemihidden \sunhideused \sqformat Normal Table;}{ -\s15\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0 \rtlch\fcs1 \af0\afs24\alang1025 \ltrch\fcs0 \fs24\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 -\sbasedon0 \snext15 \ssemihidden \sunhideused \styrsid5636368 Normal (Web);}{\*\cs16 \additive \rtlch\fcs1 \af0 \ltrch\fcs0 \ul\cf2 \sbasedon10 \ssemihidden \sunhideused \styrsid5636368 Hyperlink;}}{\*\listtable{\list\listtemplateid1243628992{\listlevel -\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\levelspace0\levelindent0{\leveltext\'01\u-3913 ?;}{\levelnumbers;}\f3\fs20\fbias0\hres0\chhres0 \fi-360\li720\jclisttab\tx720\lin720 }{\listlevel\levelnfc23\levelnfcn23\leveljc0 -\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01o;}{\levelnumbers;}\f2\fs20\fbias0\hres0\chhres0 \fi-360\li1440\jclisttab\tx1440\lin1440 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0 -\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li2160\jclisttab\tx2160\lin2160 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1 -\lvltentative\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li2880\jclisttab\tx2880\lin2880 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative -\levelspace0\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li3600\jclisttab\tx3600\lin3600 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0 -\levelindent0{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li4320\jclisttab\tx4320\lin4320 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0 -{\leveltext\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li5040\jclisttab\tx5040\lin5040 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li5760\jclisttab\tx5760\lin5760 }{\listlevel\levelnfc23\levelnfcn23\leveljc0\leveljcn0\levelfollow0\levelstartat1\lvltentative\levelspace0\levelindent0{\leveltext -\'01\u-3929 ?;}{\levelnumbers;}\f10\fs20\fbias0\hres0\chhres0 \fi-360\li6480\jclisttab\tx6480\lin6480 }{\listname ;}\listid1905722066}}{\*\listoverridetable{\listoverride\listid1905722066\listoverridecount0\ls1}}{\*\pgptbl {\pgp\ipgp0\itap0\li0\ri0\sb0 -\sa0}}{\*\rsidtbl \rsid4718992\rsid5636368\rsid15482456}{\mmathPr\mmathFont34\mbrkBin0\mbrkBinSub0\msmallFrac0\mdispDef1\mlMargin0\mrMargin0\mdefJc1\mwrapIndent1440\mintLim0\mnaryLim1}{\info{\author jeffrey.lai}{\operator jeffrey.lai} -{\creatim\yr2012\mo1\dy18\hr13\min16}{\revtim\yr2012\mo2\dy6\hr16\min32}{\version2}{\edmins4}{\nofpages2}{\nofwords331}{\nofchars1893}{\*\company Salesforce.com}{\nofcharsws2220}{\vern32771}}{\*\xmlnstbl {\xmlns1 http://schemas.microsoft.com/office/word/2 -003/wordml}}\paperw12240\paperh15840\margl1440\margr1440\margt1440\margb1440\gutter0\ltrsect -\widowctrl\ftnbj\aenddoc\trackmoves1\trackformatting1\donotembedsysfont1\relyonvml0\donotembedlingdata0\grfdocevents0\validatexml1\showplaceholdtext0\ignoremixedcontent0\saveinvalidxml0\showxmlerrors1\noxlattoyen -\expshrtn\noultrlspc\dntblnsbdb\nospaceforul\formshade\horzdoc\dgmargin\dghspace180\dgvspace180\dghorigin1440\dgvorigin1440\dghshow1\dgvshow1 -\jexpand\viewkind1\viewscale100\pgbrdrhead\pgbrdrfoot\splytwnine\ftnlytwnine\htmautsp\nolnhtadjtbl\useltbaln\alntblind\lytcalctblwd\lyttblrtgr\lnbrkrule\nobrkwrptbl\snaptogridincell\allowfieldendsel\wrppunct -\asianbrkrule\rsidroot5636368\newtblstyruls\nogrowautofit\usenormstyforlist\noindnmbrts\felnbrelev\nocxsptable\indrlsweleven\noafcnsttbl\afelev\utinl\hwelev\spltpgpar\notcvasp\notbrkcnstfrctbl\notvatxbx\krnprsnet\cachedcolbal \nouicompat \fet0 -{\*\wgrffmtfilter 2450}\nofeaturethrottle1\ilfomacatclnup0\ltrpar \sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid15482456\sftnbj {\*\pnseclvl1\pnucrm\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl2 -\pnucltr\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl3\pndec\pnstart1\pnindent720\pnhang {\pntxta .}}{\*\pnseclvl4\pnlcltr\pnstart1\pnindent720\pnhang {\pntxta )}}{\*\pnseclvl5\pndec\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl6 -\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl7\pnlcrm\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl8\pnlcltr\pnstart1\pnindent720\pnhang {\pntxtb (}{\pntxta )}}{\*\pnseclvl9\pnlcrm\pnstart1\pnindent720\pnhang -{\pntxtb (}{\pntxta )}}\pard\plain \ltrpar\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5636368 \rtlch\fcs1 \af31507\afs22\alang1025 \ltrch\fcs0 -\f31506\fs22\lang1033\langfe1033\cgrid\langnp1033\langfenp1033 {\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid5636368\charrsid5636368 End User License Agreement}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 \line -Force.com WSC version 2}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4718992 4}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 .0 -\par Except as described below, the wsc jar files are Copyright (c) 2005-2012, salesforce.com, inc. All rights reserved. -\par Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0 \ltrch\fcs0 \f3\fs20\insrsid5636368\charrsid5636368 \loch\af3\dbch\af0\hich\f3 \'b7\tab}}\pard \ltrpar\ql \fi-360\li720\ri0\sb100\sa100\sbauto1\saauto1\widctlpar -\jclisttab\tx720\wrapdefault\aspalpha\aspnum\faauto\ls1\adjustright\rin0\lin720\itap0\pararsid5636368 {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 -Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0 \ltrch\fcs0 \f3\fs20\insrsid5636368\charrsid5636368 \loch\af3\dbch\af0\hich\f3 \'b7\tab} -Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -\par {\listtext\pard\plain\ltrpar \rtlch\fcs1 \af0 \ltrch\fcs0 \f3\fs20\insrsid5636368\charrsid5636368 \loch\af3\dbch\af0\hich\f3 \'b7\tab} -Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -\par }\pard \ltrpar\ql \li0\ri0\sb100\sa100\sbauto1\saauto1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5636368 {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - -OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMA -GE. -\par The following Third Party components are licensed as follows: -\par }{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid5636368\charrsid5636368 Xml Pull Parser}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 -\par MXParser.java\line MXSerializer.java\line XmlPullParser.java\line XmlPullParserException.java -\par Your use of the Xml Pull Parser code is subject to the terms and conditions of the Indiana University Extreme! Lab Software License, Version 1.2, available at }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 -\f0\fs24\insrsid5636368\charrsid5636368 HYPERLINK "http://extreme.indiana.edu/license.txt" }{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4718992\charrsid5636368 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b6600000068007400740070003a002f002f00650078007400720065006d0065002e0069006e006400690061006e0061002e006500640075002f006c006900630065006e00730065002e007400780074000000795881f4 -3b1d7f48af2c825dc485276300000000a5ab0000}}}{\fldrslt {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\ul\cf2\insrsid5636368\charrsid5636368 http://extreme.indiana.edu/license.txt}}}\sectd \ltrsect -\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid15482456\sftnbj {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 . -\par }{\rtlch\fcs1 \ab\af0\afs24 \ltrch\fcs0 \b\f0\fs24\insrsid5636368\charrsid5636368 Rhino: JavaScript for Java}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 -\par js-1.7R2.jar -\par Your use of the Rhino code is subject to the terms and conditions of the Mozilla Public License, available at }{\field\fldedit{\*\fldinst {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid5636368\charrsid5636368 HYPERLINK "http://www.mozilla.org/MPL/" -}{\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\insrsid4718992\charrsid5636368 {\*\datafield -00d0c9ea79f9bace118c8200aa004ba90b0200000003000000e0c9ea79f9bace118c8200aa004ba90b5000000068007400740070003a002f002f007700770077002e006d006f007a0069006c006c0061002e006f00720067002f004d0050004c002f000000795881f43b1d7f48af2c825dc485276300000000a5ab0000}} -}{\fldrslt {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 \f0\fs24\ul\cf2\insrsid5636368\charrsid5636368 http://www.mozilla.org/MPL/}}}\sectd \ltrsect\linex0\endnhere\sectlinegrid360\sectdefaultcl\sectrsid15482456\sftnbj {\rtlch\fcs1 \af0\afs24 \ltrch\fcs0 -\f0\fs24\insrsid5636368\charrsid5636368 . -\par }\pard \ltrpar\ql \li0\ri0\sa200\sl276\slmult1\widctlpar\wrapdefault\aspalpha\aspnum\faauto\adjustright\rin0\lin0\itap0\pararsid5636368 {\rtlch\fcs1 \af31507 \ltrch\fcs0 \insrsid15482456\charrsid5636368 -\par }{\*\themedata 504b030414000600080000002100828abc13fa0000001c020000130000005b436f6e74656e745f54797065735d2e786d6cac91cb6ac3301045f785fe83d0b6d8 -72ba28a5d8cea249777d2cd20f18e4b12d6a8f843409c9df77ecb850ba082d74231062ce997b55ae8fe3a00e1893f354e9555e6885647de3a8abf4fbee29bbd7 -2a3150038327acf409935ed7d757e5ee14302999a654e99e393c18936c8f23a4dc072479697d1c81e51a3b13c07e4087e6b628ee8cf5c4489cf1c4d075f92a0b -44d7a07a83c82f308ac7b0a0f0fbf90c2480980b58abc733615aa2d210c2e02cb04430076a7ee833dfb6ce62e3ed7e14693e8317d8cd0433bf5c60f53fea2fe7 -065bd80facb647e9e25c7fc421fd2ddb526b2e9373fed4bb902e182e97b7b461e6bfad3f010000ffff0300504b030414000600080000002100a5d6a7e7c00000 -00360100000b0000005f72656c732f2e72656c73848fcf6ac3300c87ef85bd83d17d51d2c31825762fa590432fa37d00e1287f68221bdb1bebdb4fc7060abb08 -84a4eff7a93dfeae8bf9e194e720169aaa06c3e2433fcb68e1763dbf7f82c985a4a725085b787086a37bdbb55fbc50d1a33ccd311ba548b63095120f88d94fbc -52ae4264d1c910d24a45db3462247fa791715fd71f989e19e0364cd3f51652d73760ae8fa8c9ffb3c330cc9e4fc17faf2ce545046e37944c69e462a1a82fe353 -bd90a865aad41ed0b5b8f9d6fd010000ffff0300504b0304140006000800000021006b799616830000008a0000001c0000007468656d652f7468656d652f7468 -656d654d616e616765722e786d6c0ccc4d0ac3201040e17da17790d93763bb284562b2cbaebbf600439c1a41c7a0d29fdbd7e5e38337cedf14d59b4b0d592c9c -070d8a65cd2e88b7f07c2ca71ba8da481cc52c6ce1c715e6e97818c9b48d13df49c873517d23d59085adb5dd20d6b52bd521ef2cdd5eb9246a3d8b4757e8d3f7 -29e245eb2b260a0238fd010000ffff0300504b03041400060008000000210096b5ade296060000501b0000160000007468656d652f7468656d652f7468656d65 -312e786d6cec594f6fdb3614bf0fd87720746f6327761a07758ad8b19b2d4d1bc46e871e698996d850a240d2497d1bdae38001c3ba618715d86d87615b8116d8 -a5fb34d93a6c1dd0afb0475292c5585e9236d88aad3e2412f9e3fbff1e1fa9abd7eec70c1d1221294fda5efd72cd4324f1794093b0eddd1ef62fad79482a9c04 -98f184b4bd2991deb58df7dfbb8ad755446282607d22d771db8b944ad79796a40fc3585ee62949606ecc458c15bc8a702910f808e8c66c69b9565b5d8a314d3c -94e018c8de1a8fa94fd05093f43672e23d06af89927ac06762a049136785c10607758d9053d965021d62d6f6804fc08f86e4bef210c352c144dbab999fb7b471 -7509af678b985ab0b6b4ae6f7ed9ba6c4170b06c788a705430adf71bad2b5b057d03606a1ed7ebf5babd7a41cf00b0ef83a6569632cd467faddec9699640f671 -9e76b7d6ac355c7c89feca9cccad4ea7d36c65b258a206641f1b73f8b5da6a6373d9c11b90c537e7f08dce66b7bbeae00dc8e257e7f0fd2badd5868b37a088d1 -e4600ead1ddaef67d40bc898b3ed4af81ac0d76a197c86826828a24bb318f3442d8ab518dfe3a20f000d6458d104a9694ac6d88728eee2782428d60cf03ac1a5 -193be4cbb921cd0b495fd054b5bd0f530c1931a3f7eaf9f7af9e3f45c70f9e1d3ff8e9f8e1c3e3073f5a42ceaa6d9c84e5552fbffdeccfc71fa33f9e7ef3f2d1 -17d57859c6fffac327bffcfc793510d26726ce8b2f9ffcf6ecc98baf3efdfdbb4715f04d814765f890c644a29be408edf3181433567125272371be15c308d3f2 -8acd249438c19a4b05fd9e8a1cf4cd296699771c393ac4b5e01d01e5a30a787d72cf1178108989a2159c77a2d801ee72ce3a5c545a6147f32a99793849c26ae6 -6252c6ed637c58c5bb8b13c7bfbd490a75330f4b47f16e441c31f7184e140e494214d273fc80900aedee52ead87597fa824b3e56e82e451d4c2b4d32a423279a -668bb6690c7e9956e90cfe766cb37b077538abd27a8b1cba48c80acc2a841f12e698f13a9e281c57911ce298950d7e03aba84ac8c154f8655c4f2af074481847 -bd804859b5e696007d4b4edfc150b12addbecba6b18b148a1e54d1bc81392f23b7f84137c2715a851dd0242a633f900710a218ed715505dfe56e86e877f0034e -16bafb0e258ebb4faf06b769e888340b103d3311da9750aa9d0a1cd3e4efca31a3508f6d0c5c5c398602f8e2ebc71591f5b616e24dd893aa3261fb44f95d843b -5974bb5c04f4edafb95b7892ec1108f3f98de75dc97d5772bdff7cc95d94cf672db4b3da0a6557f70db629362d72bcb0431e53c6066acac80d699a6409fb44d0 -8741bdce9c0e4971624a2378cceaba830b05366b90e0ea23aaa241845368b0eb9e2612ca8c742851ca251ceccc70256d8d87265dd96361531f186c3d9058edf2 -c00eafe8e1fc5c509031bb4d680e9f39a3154de0accc56ae644441edd76156d7429d995bdd88664a9dc3ad50197c38af1a0c16d684060441db02565e85f3b966 -0d0713cc48a0ed6ef7dedc2dc60b17e92219e180643ed27acffba86e9c94c78ab90980d8a9f0913ee49d62b512b79626fb06dccee2a432bbc60276b9f7dec44b -7904cfbca4f3f6443ab2a49c9c2c41476dafd55c6e7ac8c769db1bc399161ee314bc2e75cf8759081743be1236ec4f4d6693e5336fb672c5dc24a8c33585b5fb -9cc24e1d4885545b58463634cc5416022cd19cacfccb4d30eb45296023fd35a458598360f8d7a4003bbaae25e331f155d9d9a5116d3bfb9a95523e51440ca2e0 -088dd844ec6370bf0e55d027a012ae264c45d02f708fa6ad6da6dce29c255df9f6cae0ec38666984b372ab5334cf640b37795cc860de4ae2816e95b21be5ceaf -8a49f90b52a51cc6ff3355f47e0237052b81f6800fd7b802239daf6d8f0b1571a8426944fdbe80c6c1d40e8816b88b8569082ab84c36ff0539d4ff6dce591a26 -ade1c0a7f669880485fd484582903d284b26fa4e2156cff62e4b9265844c4495c495a9157b440e091bea1ab8aaf7760f4510eaa69a6465c0e04ec69ffb9e65d0 -28d44d4e39df9c1a52ecbd3607fee9cec7263328e5d661d3d0e4f62f44acd855ed7ab33cdf7bcb8ae889599bd5c8b3029895b6825696f6af29c239b75a5bb1e6 -345e6ee6c28117e73586c1a2214ae1be07e93fb0ff51e133fb65426fa843be0fb515c187064d0cc206a2fa926d3c902e907670048d931db4c1a44959d366ad93 -b65abe595f70a75bf03d616c2dd959fc7d4e6317cd99cbcec9c58b34766661c7d6766ca1a9c1b327531486c6f941c638c67cd22a7f75e2a37be0e82db8df9f30 -254d30c1372581a1f51c983c80e4b71ccdd28dbf000000ffff0300504b0304140006000800000021000dd1909fb60000001b010000270000007468656d652f74 -68656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73848f4d0ac2301484f78277086f6fd3ba109126dd88d0add40384e4350d363f24 -51eced0dae2c082e8761be9969bb979dc9136332de3168aa1a083ae995719ac16db8ec8e4052164e89d93b64b060828e6f37ed1567914b284d262452282e3198 -720e274a939cd08a54f980ae38a38f56e422a3a641c8bbd048f7757da0f19b017cc524bd62107bd5001996509affb3fd381a89672f1f165dfe514173d9850528 -a2c6cce0239baa4c04ca5bbabac4df000000ffff0300504b01022d0014000600080000002100828abc13fa0000001c0200001300000000000000000000000000 -000000005b436f6e74656e745f54797065735d2e786d6c504b01022d0014000600080000002100a5d6a7e7c0000000360100000b000000000000000000000000 -002b0100005f72656c732f2e72656c73504b01022d00140006000800000021006b799616830000008a0000001c00000000000000000000000000140200007468 -656d652f7468656d652f7468656d654d616e616765722e786d6c504b01022d001400060008000000210096b5ade296060000501b000016000000000000000000 -00000000d10200007468656d652f7468656d652f7468656d65312e786d6c504b01022d00140006000800000021000dd1909fb60000001b010000270000000000 -00000000000000009b0900007468656d652f7468656d652f5f72656c732f7468656d654d616e616765722e786d6c2e72656c73504b050600000000050005005d010000960a00000000} -{\*\colorschememapping 3c3f786d6c2076657273696f6e3d22312e302220656e636f64696e673d225554462d3822207374616e64616c6f6e653d22796573223f3e0d0a3c613a636c724d -617020786d6c6e733a613d22687474703a2f2f736368656d61732e6f70656e786d6c666f726d6174732e6f72672f64726177696e676d6c2f323030362f6d6169 -6e22206267313d226c743122207478313d22646b3122206267323d226c743222207478323d22646b322220616363656e74313d22616363656e74312220616363 -656e74323d22616363656e74322220616363656e74333d22616363656e74332220616363656e74343d22616363656e74342220616363656e74353d22616363656e74352220616363656e74363d22616363656e74362220686c696e6b3d22686c696e6b2220666f6c486c696e6b3d22666f6c486c696e6b222f3e} -{\*\latentstyles\lsdstimax267\lsdlockeddef0\lsdsemihiddendef1\lsdunhideuseddef1\lsdqformatdef0\lsdprioritydef99{\lsdlockedexcept \lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority0 \lsdlocked0 Normal; -\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority9 \lsdlocked0 heading 1;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 2;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 3;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 4; -\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 5;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 6;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 7;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 8;\lsdqformat1 \lsdpriority9 \lsdlocked0 heading 9; -\lsdpriority39 \lsdlocked0 toc 1;\lsdpriority39 \lsdlocked0 toc 2;\lsdpriority39 \lsdlocked0 toc 3;\lsdpriority39 \lsdlocked0 toc 4;\lsdpriority39 \lsdlocked0 toc 5;\lsdpriority39 \lsdlocked0 toc 6;\lsdpriority39 \lsdlocked0 toc 7; -\lsdpriority39 \lsdlocked0 toc 8;\lsdpriority39 \lsdlocked0 toc 9;\lsdqformat1 \lsdpriority35 \lsdlocked0 caption;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority10 \lsdlocked0 Title;\lsdpriority1 \lsdlocked0 Default Paragraph Font; -\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority11 \lsdlocked0 Subtitle;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority22 \lsdlocked0 Strong;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority20 \lsdlocked0 Emphasis; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority59 \lsdlocked0 Table Grid;\lsdunhideused0 \lsdlocked0 Placeholder Text;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority1 \lsdlocked0 No Spacing; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 1; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 1; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 1;\lsdunhideused0 \lsdlocked0 Revision; -\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority34 \lsdlocked0 List Paragraph;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority29 \lsdlocked0 Quote;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority30 \lsdlocked0 Intense Quote; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 1; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 1; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 1;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 2; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 2; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 2; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 2; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 2; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 2;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 3; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 3; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 3; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 3; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 3;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 3; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 4; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 4; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 4; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 4; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 4;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 5; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 5; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 5; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 5; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 5; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 5;\lsdsemihidden0 \lsdunhideused0 \lsdpriority60 \lsdlocked0 Light Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority61 \lsdlocked0 Light List Accent 6; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority62 \lsdlocked0 Light Grid Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority63 \lsdlocked0 Medium Shading 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority64 \lsdlocked0 Medium Shading 2 Accent 6; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority65 \lsdlocked0 Medium List 1 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority66 \lsdlocked0 Medium List 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority67 \lsdlocked0 Medium Grid 1 Accent 6; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority68 \lsdlocked0 Medium Grid 2 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority69 \lsdlocked0 Medium Grid 3 Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority70 \lsdlocked0 Dark List Accent 6; -\lsdsemihidden0 \lsdunhideused0 \lsdpriority71 \lsdlocked0 Colorful Shading Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority72 \lsdlocked0 Colorful List Accent 6;\lsdsemihidden0 \lsdunhideused0 \lsdpriority73 \lsdlocked0 Colorful Grid Accent 6; -\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority19 \lsdlocked0 Subtle Emphasis;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority21 \lsdlocked0 Intense Emphasis; -\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority31 \lsdlocked0 Subtle Reference;\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority32 \lsdlocked0 Intense Reference; -\lsdsemihidden0 \lsdunhideused0 \lsdqformat1 \lsdpriority33 \lsdlocked0 Book Title;\lsdpriority37 \lsdlocked0 Bibliography;\lsdqformat1 \lsdpriority39 \lsdlocked0 TOC Heading;}}{\*\datastore 010500000200000018000000 -4d73786d6c322e534158584d4c5265616465722e352e3000000000000000000000060000 -d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff090006000000000000000000000001000000010000000000000000100000feffffff00000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -fffffffffffffffffdfffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffffffffffffec69d9888b8b3d4c859eaf6cd158be0f000000000000000000000000f087 -4bef2fe5cc01feffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000 -000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000105000000000000}} \ No newline at end of file diff --git a/src/main/nsis/samples/conf/database-conf.xml b/src/main/nsis/samples/conf/database-conf.xml deleted file mode 100644 index 7d0f4211a..000000000 --- a/src/main/nsis/samples/conf/database-conf.xml +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SELECT ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER - FROM TableOwner.Accounts - - - - - account_name - business_phone - sfdc_account_id - account_ext_id - annual_revenue - last_updated - account_number - - - - - - - SELECT ACCOUNT_NAME, BUSINESS_PHONE, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER - FROM TableOwner.Accounts - WHERE LAST_UPDATED > @process.lastRunDate@ - - - - - account_name - business_phone - account_ext_id - annual_revenue - last_updated - account_number - - - - - - - - - - - - - update TableOwner.Accounts accounts - set accounts.account_name = @account_name@, - accounts.business_phone = @business_phone@, - accounts.sfdc_account_id = @sfdc_account_id@, - accounts.annual_revenue = @annual_revenue@, - accounts.account_number = @account_number@ - where - accounts.ACCOUNT_EXT_ID = @account_ext_id@ - - - - - - - - - - - - - - - - - INSERT INTO TableOwner.Accounts ( - ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ANNUAL_REVENUE, ACCOUNT_EXT_ID, ACCOUNT_NUMBER) - VALUES (@account_name@, @business_phone@, @sfdc_account_id@, @annual_revenue@, @account_ext_id@, @account_number@) - - - - - - - - - - - - - - - - - DELETE FROM TableOwner.Accounts - - - - diff --git a/src/main/nsis/samples/conf/log-conf.xml b/src/main/nsis/samples/conf/log-conf.xml deleted file mode 100644 index 1ce230472..000000000 --- a/src/main/nsis/samples/conf/log-conf.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/nsis/samples/conf/process-conf.xml b/src/main/nsis/samples/conf/process-conf.xml deleted file mode 100644 index bf3cc76c5..000000000 --- a/src/main/nsis/samples/conf/process-conf.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'. - - - - - - - - - - - - - - - - - - - - - - - - Opportunity Upsert job gets the Customer record updates from a CSV file and uploads them to salesforce using 'upsert'. - - - - - - - - - - - - - - - - - - - - - - DatabaseAccountExtract job gets account info from salesforce and updates or inserts info into database." - - - - - - - - - - - - - - - - - - - - - - - csvAccountExtract job gets account info from salesforce and saves info into a CSV file." - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/nsis/samples/conf/sample.key b/src/main/nsis/samples/conf/sample.key deleted file mode 100644 index 491f66633..000000000 --- a/src/main/nsis/samples/conf/sample.key +++ /dev/null @@ -1 +0,0 @@ -380db410e8b11fa9 diff --git a/src/main/resources/Localizable.strings b/src/main/resources/Localizable.strings index 3a43bd928..45ae74b27 100644 --- a/src/main/resources/Localizable.strings +++ b/src/main/resources/Localizable.strings @@ -1 +1 @@ -"JRELoadError" = "Java 1.8 is required but could not be found. Please visit http://java.com/download"; \ No newline at end of file +"JRELoadError" = "Java runtime is required but could not be found. Please install the openjdk11+"; \ No newline at end of file diff --git a/src/main/resources/com/salesforce/dataloader/version.properties b/src/main/resources/com/salesforce/dataloader/version.properties index 4970a8401..ef9434fa4 100644 --- a/src/main/resources/com/salesforce/dataloader/version.properties +++ b/src/main/resources/com/salesforce/dataloader/version.properties @@ -1,3 +1,4 @@ dataloader.name = ${project.name} dataloader.version = ${project.version} -dataloader.vendor = ${organization.name} \ No newline at end of file +dataloader.vendor = ${organization.name} +java.min.version = ${maven.compiler.release} \ No newline at end of file diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties new file mode 100644 index 000000000..439aebe60 --- /dev/null +++ b/src/main/resources/config.properties @@ -0,0 +1,2 @@ +#Salesforce Data Loader Config +#Mon Feb 11 11:13:44 PST 2019 diff --git a/src/main/resources/img/dataloader-title-logo.png b/src/main/resources/img/dataloader-title-logo.png index f2b6a7b82..8f6c63846 100644 Binary files a/src/main/resources/img/dataloader-title-logo.png and b/src/main/resources/img/dataloader-title-logo.png differ diff --git a/src/main/resources/labels.properties b/src/main/resources/labels.properties index 9ba5619c9..e70732dd2 100644 --- a/src/main/resources/labels.properties +++ b/src/main/resources/labels.properties @@ -1,6 +1,6 @@ -CSVChooser.title=CSV Chooser +CSVChooserDialog.title=CSV Chooser CSVViewer.title=CSV Viewer -CSVChooser.message=\ \ \ Choose the CSV to view: +CSVChooserDialog.message=\ \ \ Choose the CSV to view: CSVChooser.numberRows=Select the number of rows to view: CSVChooser.selectOpen=Select a CSV to open: CSVChooser.openCsv=Open CSV @@ -9,6 +9,7 @@ CSVChooser.lastSuccess=Open the last success file: CSVChooser.openSuccess=Open Success CSVChooser.lastError=Open the last error file: CSVChooser.openError=Open Error + UI.ok=OK UI.cancel=Cancel UI.close=Close @@ -16,6 +17,7 @@ UI.insert=Insert UI.update=Update UI.upsert=Upsert UI.delete=Delete +UI.undelete=Undelete UI.hard_delete=Hard Delete UI.extract=Export UI.extract_all=Export All @@ -23,243 +25,496 @@ UI.warning=Warning UI.error=Error UI.message=Info UI.fileAlreadyExists=The file you have selected already exists. Would you like to replace the existing file? +UI.contentUploadLimit=Make sure that the Content files to be uploaded are within specified limits + DataSelectionDialog.title=Data Selection DataSelectionDialog.titleError=Data Selection Error DataSelectionDialog.titleWarning=Data Selection Warning DataSelectionDialog.warningConf=Would you like to continue? DataSelectionDialog.message=Initializing Salesforce object and CSV file. -DataSelectionPage.data=Data -DataSelectionPage.csv=CSV -DataSelectionPage.hard_delete=You need the Bulk API Hard Delete user permission to permanently delete data. -DataSelectionDialog.initSuccess=Initialization succeeded. \nYour operation will contain {0} records. +DataSelectionDialog.initSuccess=Initialization succeeded. \nThe CSV file has {0} rows. DataSelectionDialog.errorEntity=Salesforce error validating object. -DataSelectionDialog.errorRead=CSV Error: Can't read from CSV file. -DataSelectionDialog.errorCSVFormat=CSV Error: Invalid CSV file format. Please select a different file. -DataSelectionPage.dataMsg=Step 2: Select data objects -DataSelectionPage.message=Select your Salesforce object and your CSV file. -DataSelectionPage.selectObject=Select Salesforce object: -DataSelectionPage.showAll=&Show all Salesforce objects -DataSelectionPage.csvMessage=Choose CSV file: -DataSelectionPage.selectValid=Please select a valid CSV file. -DataSelectionPage.errorClassCast=Class cast in listener -DataSelectionPage.entity=Object: +DataSelectionDialog.errorRead=CSV Error: Can't read from CSV file.\n\n +DataSelectionDialog.errorReadExceptionDetails=Details:\n{0} +DataSelectionDialog.errorReadPermissionDetails=Make sure that the file has read permission for Java Runtime +DataSelectionDialog.errorCSVFormat=CSV Error: Invalid CSV file format.\n\n +DataSelectionDialog.errorCSVDetails=Make sure that delimiter settings are correctly specified in Settings dialog or select a different file. Current delimiters are {0} + delete.UIAction.menuText=&Delete@Ctrl+D delete.UIAction.toolTipText=Delete Records -DeleteWizard.title=Load Deletes + +DeleteWizard.windowTitle=Load Deletes DeleteWizard.confFirstLine=You have chosen to delete existing records. Click Yes to begin. DeleteWizard.confSecondLine=Do you want to proceed? DeleteWizard.validateFirstLine=Warning: The number of records allowed in your Recycle Bin is limited. DeleteWizard.validateSecondLine=The action you are about to take may cause irreversible data loss to records that may exist in your Recycle Bin. \nAre you sure you want to continue? + +undelete.UIAction.menuText=&Undelete@Ctrl+N +undelete.UIAction.toolTipText=Undelete Records + +UndeleteWizard.windowTitle=Load Undeletes +UndeleteWizard.confFirstLine=You have chosen to undelete deleted records. Some or all of them may already be permanently deleted, in which case they can't be undeleted. Click Yes to begin. +UndeleteWizard.confSecondLine=Do you want to proceed? + hard_delete.UIAction.menuText=&Hard Delete@Ctrl+H hard_delete.UIAction.toolTipText=Permanently Delete Records -HardDeleteWizard.title=Load Hard Deletes + +HardDeleteWizard.windowTitle=Load Hard Deletes HardDeleteWizard.confFirstLine=You are about to permanently delete data. This can't be undone and the records will not be available in your Recycle Bin. HardDeleteWizard.confSecondLine=You need the Bulk API Hard Delete user permission to permanently delete data. Do you want to proceed? HardDeleteWizard.finishMessage=You are about to permanently delete data. This can't be undone and the records will not be available in your Recycle Bin. HardDeleteWizard.finishMessageConfirm=I understand. Let me finish. -FinishPage.title=Finish -FinishPage.finishMsg=Step 4: Finish -FinishPage.selectDir=Select the directory where your success and error files will be saved. -FinishPage.overwritten= -FinishPage.output=Output -FinishPage.chooseDir=Directory: + +SettingsHelpDialog.settingsHelp= \ + Settings are a subset of all Configurable Properties of Data Loader.\n\ + Configurable Properties can be set by specifying them in config.properties file, process-conf.xml file, as command line options, \ + or in the Settings dialog.

\n\ + \ +

  • Properties that are modifiable are also referred to as settings. Most of the settings are shown in Settings dialog. Some settings such as sfdc.ui.wizard.width that are determined as the user moves or resizes a wizard dialog.

  • \n\ +
  • All configurable properties that are settable through Settings dialog are settable through config.properties file.

  • \n\ +
  • All configurable properties that are settable through config.properties file are settable through process-conf.xml and vice versa. Property settings in process-conf.xml override those in config.properties file.

  • \n\ +
  • Some configurable properties are only settable as command line options.

  • \n\ +
  • All properties that are settable through config.properties file are also settable as command line options. Property values specified through command line options override values specified in config.properties file.

  • \n\ +
  • All changes to config.properties file should be made after stopping Data Loader because changes to config.properties file are not picked up if Data Loader is running. Moreover, the running Data Loader may override all of these changes.

  • \n\ + +SettingsHelpDialog.getPropsCSV=The CSV file containing all configurable properties is located at \n{0} +SettingsHelpDialog.title=Help - Configurable Properties including Settings + AdvancedSettingsDialog.title=Settings -AdvancedSettingsDialog.message=\ \ Modify your settings: -AdvancedSettingsDialog.batchSize=Batch size: -AdvancedSettingsDialog.insertNulls=Insert null values: -AdvancedSettingsDialog.assignmentRule=Assignment rule: -AdvancedSettingsDialog.externalIdField=External ID field: -AdvancedSettingsDialog.useEuropeanDateFormat=Use European date format (dd/mm/yyyy) -AdvancedSettingsDialog.serverURL=Server host (clear for default): -AdvancedSettingsDialog.sessionTimeout=Session Timeout (in seconds): -AdvancedSettingsDialog.compression=Compression (check to turn off): -AdvancedSettingsDialog.resetUrlOnLogin=Reset URL on Login (clear to turn off): -AdvancedSettingsDialog.timeout=Timeout (in seconds): -AdvancedSettingsDialog.timezone=Time Zone: -AdvancedSettingsDialog.proxyHost=Proxy host: -AdvancedSettingsDialog.proxyPort=Proxy port: -AdvancedSettingsDialog.proxyNtlmDomain=Proxy NTLM domain: -AdvancedSettingsDialog.proxyUser=Proxy username: -AdvancedSettingsDialog.lastBatch=The last batch finished at {0}. Use {0} to continue from your last location. -AdvancedSettingsDialog.outputExtractStatus=Generate status files for exports: -AdvancedSettingsDialog.readUTF8=Read all CSVs with UTF-8 encoding: -AdvancedSettingsDialog.writeUTF8=Write all CSVs with UTF-8 encoding: -AdvancedSettingsDialog.proxyPassword=Proxy password: -AdvancedSettingsDialog.startRow=Start at row: -AdvancedSettingsDialog.allowFieldTruncation=Allow field truncation: -AdvancedSettingsDialog.useBulkApi=Use Bulk API: -AdvancedSettingsDialog.bulkApiSerialMode=Enable serial mode for Bulk API: -AdvancedSettingsDialog.bulkApiZipContent=Upload Bulk API Batch as Zip File (enable to upload binary attachments): -AdvancedSettingsDialog.hideWelcomeScreen=Hide Welcome screen: -AdvancedSettingsDialog.useCommaAsCsvDelimiter=Allow comma as a CSV delimiter: -AdvancedSettingsDialog.useTabAsCsvDelimiter=Allow Tab as a CSV delimiter: -AdvancedSettingsDialog.useOtherAsCsvDelimiter=Allow other characters as CSV delimiters: -AdvancedSettingsDialog.csvOtherDelimiterValue=Other Delimiters (enter multiple values with no separator; for example, !+?): - -InsertWizard.title=Load Inserts +AdvancedSettingsDialog.uiLabel.message= Modify your settings +AdvancedSettingsDialog.uiLabel.sfdc.loadBatchSize=Import batch size:\n(max {1}) +AdvancedSettingsDialog.uiLabel.sfdc.extractionRequestSize=Export batch size:\n(min {0}, max {1}) +AdvancedSettingsDialog.uiLabel.sfdc.insertNulls=Insert null values: +AdvancedSettingsDialog.uiTooltip.sfdc.insertNulls=interpret blank values in mapped CSV fields as null instead of skipping them during insert/update/upsert operations +AdvancedSettingsDialog.uiLabel.sfdc.assignmentRule=Assignment rule id for Cases/Leads import: +AdvancedSettingsDialog.uiLabel.externalIdField=External ID field: +AdvancedSettingsDialog.uiLabel.process.useEuropeanDates=Use European date format:\n(dd/mm/yyyy) +AdvancedSettingsDialog.uiLabel.sfdc.endpoint.Production=Authentication host domain URL for Production:\n(aka Server host URL. Clear for default) +AdvancedSettingsDialog.uiLabel.sfdc.endpoint.Sandbox=Authentication host domain URL for Sandbox:\n(aka Server host URL. Clear for default) +AdvancedSettingsDialog.uiLabel.serverURLInfo=Enter an authentication host domain URL that uses HTTPS protocol.\nFor example, "https://login.salesforce.com" is valid.\n\"{0}\" is invalid. +AdvancedSettingsDialog.uiLabel.sessionTimeout=Session Timeout:\n(in seconds) +AdvancedSettingsDialog.uiLabel.sfdc.noCompression=Disable data compression during upload to the server: +AdvancedSettingsDialog.uiTooltip.sfdc.noCompression=Compression enhances the performance of Data Loader and is turned on by default. Disabling compression is helpful when debugging the underlying SOAP messages. To turn off compression, enable this option. +AdvancedSettingsDialog.uiLabel.resetUrlOnLogin=Reset URL on Login:\n(clear to turn off) +AdvancedSettingsDialog.uiLabel.sfdc.timeoutSecs=Server response timeout:\n(in seconds) +AdvancedSettingsDialog.uiTooltip.sfdc.timeoutSecs=Specify how many seconds Data Loader waits to receive a response back from the server before returning an error for the request. +AdvancedSettingsDialog.uiLabel.sfdc.timezone=Time Zone: +AdvancedSettingsDialog.uiLabel.setClientSystemTimezone=Reset +AdvancedSettingsDialog.uiTooltip.SetClientSystemTimezone=Reset Time Zone to desktop's system time zone +AdvancedSettingsDialog.uiLabel.sfdc.proxyHost=Proxy host: +AdvancedSettingsDialog.uiLabel.sfdc.proxyPort=Proxy port: +AdvancedSettingsDialog.uiLabel.sfdc.proxyNtlmDomain=Proxy NTLM domain: +AdvancedSettingsDialog.uiLabel.sfdc.proxyUsername=Proxy username: +AdvancedSettingsDialog.uiLabel.process.lastLoadBatchRow=The last batch finished at {0}\nEnter {0} to continue from your last location. + +AdvancedSettingsDialog.uiLabel.sfdc.sortExtractionFields=Sort Salesforce object field names: \nwhen editing SOQL for Export +AdvancedSettingsDialog.uiLabel.loader.query.limitOutputToQueryFields=Limit query result fields to the fields specified in the SOQL query: +AdvancedSettingsDialog.uiLabel.process.enableExtractStatusOutput=Generate status files for exports: +AdvancedSettingsDialog.uiLabel.dataAccess.readUTF8=Read all CSVs with UTF-8 encoding: +AdvancedSettingsDialog.uiLabel.dataAccess.writeUTF8=Write all CSVs with UTF-8 encoding: +AdvancedSettingsDialog.uiLabel.sfdc.proxyPassword=Proxy password: +AdvancedSettingsDialog.uiLabel.process.loadRowToStartAt=Start at row: +AdvancedSettingsDialog.uiLabel.sfdc.truncateFields=Allow field truncation: +AdvancedSettingsDialog.uiLabel.sfdc.formatPhoneFields=Format phone fields if the user locale is U.S. or Canada:\n(For example, "1112223333" will be formatted as "(111) 222-3333") +AdvancedSettingsDialog.uiLabel.process.keepAccountTeam=Keep Account team when changing Account owner: +AdvancedSettingsDialog.uiTooltip.process.keepAccountTeam=The uploaded .csv file must have the same current Account owner values in all rows. Additionally, it must have the same new Account Owner values in all rows. +AdvancedSettingsDialog.uiLabel.useBulkApi=Use Bulk API: +AdvancedSettingsDialog.uiLabel.sfdc.bulkApiSerialMode=Enable serial mode for Bulk API: +AdvancedSettingsDialog.uiLabel.sfdc.bulkApiZipContent=Import binary content:\n(as Zip File) +AdvancedSettingsDialog.uiLabel.useSOAPApi=Use SOAP API +AdvancedSettingsDialog.uiTooltip.useSOAPApi=Selecting it enables some of the disabled options, changes limits, enables Undelete operation, and disables Hard Delete operation.\n\nIt can also be enabled by setting {0} and {1} properties to false in config.properties file. +AdvancedSettingsDialog.uiLabel.useBulkV1Api=Use Bulk API +AdvancedSettingsDialog.uiTooltip.useBulkV1Api=Selecting it enables some of the disabled options, changes limits, enables Hard Delete operation, and disables Undelete operation.\n\nIt can also be enabled by setting {0} to true and {1} to false in config.properties file. +AdvancedSettingsDialog.uiLabel.useBulkV2Api=Use Bulk API 2.0 +AdvancedSettingsDialog.uiTooltip.useBulkV2Api=Selecting it enables some of the disabled options, changes limits,, enables Hard Delete operation, and disables Undelete operation.\n\nIt can also be enabled by setting {1} to true in config.properties file. +AdvancedSettingsDialog.uiLabel.sfdc.updateWithExternalId=Perform updates with an external id field: +AdvancedSettingsDialog.uiLabel.loader.hideWelcome=Show Welcome screen: +AdvancedSettingsDialog.uiLabel.loader.ui.showUpgrade=Show the dialog to download the latest version: +AdvancedSettingsDialog.uiLabel.loader.csvComma=Allow comma as a CSV delimiter for load operations: +AdvancedSettingsDialog.uiLabel.loader.csvTab=Allow Tab as a CSV delimiter for load operations: +AdvancedSettingsDialog.uiLabel.loader.csvOtherValue=Other Delimiters for load operations:\n(enter multiple values with no separator; for example, !+?) +AdvancedSettingsDialog.uiLabel.loader.query.delimiter=Delimiter for query results: +AdvancedSettingsDialog.uiLabel.loader.query.includeBinaryData=Include image content in query results of Rich Text Fields: +AdvancedSettingsDialog.uiLabel.escapeFormulaInFieldValue=Escape formula values in exported results with a single-quote character: +AdvancedSettingsDialog.uiLabel.sfdc.oauth.loginfrombrowser=Enable OAuth login from browser: +AdvancedSettingsDialog.uiLabel.sfdc.oauth.Production.partner.clientid=Client ID for SOAP API in Production: +AdvancedSettingsDialog.uiLabel.sfdc.oauth.Production.bulk.clientid=Client ID for Bulk API and Bulk API 2.0 in Production: +AdvancedSettingsDialog.uiLabel.sfdc.oauth.Sandbox.partner.clientid=Client ID for SOAP API in Sandbox: +AdvancedSettingsDialog.uiLabel.sfdc.oauth.Sandbox.bulk.clientid=Client ID for Bulk API and Bulk API 2.0 in Sandbox: +AdvancedSettingsDialog.uiLabel.loggingLevel=Current logging level: +AdvancedSettingsDialog.uiLabel.configDir=Folder containing configuration files:\n(config.properties, log4j2.properties, ...) +AdvancedSettingsDialog.uiLabel.latestLoggingFile=Logging output file: +AdvancedSettingsDialog.uiLabel.loggingConfigFile=Logging configuration file: +AdvancedSettingsDialog.uiLabel.checkUploadDelimiterCheckbox=Specify delimiter(s) for upload operations: +AdvancedSettingsDialog.uiLabel.loader.cacheSObjectNamesAndFields=Cache Salesforce object and field information across operations: +AdvancedSettingsDialog.uiLabel.sfdc.ui.wizard.closeOnFinish=Close UI Wizard when an operation is completed: +AdvancedSettingsDialog.uiLabel.wizardWidthAndHeight=Width and height of UI wizards:\n(width x height) +AdvancedSettingsDialog.uiLabel.enforceSpecifiedWizardWidthAndHeight=Enforce preferred width and height of UI Wizard: +AdvancedSettingsDialog.uiLabel.sfdc.ui.wizard.finishStep.prepopulateWithPreviousOpResultsFolder=Pre-populate Results Folder input field in Finish step of UI Wizard:\n (use the folder chosen in the previous operation) +AdvancedSettingsDialog.uiLabel.undeleteOperationEnabled=Undelete Operation Enabled +AdvancedSettingsDialog.uiLabel.hardDeleteOperationEnabled=Hard Delete Operation Enabled +AdvancedSettingsDialog.uiTooltip.undeleteOperationEnabled=Select SOAP API to enable Undelete +AdvancedSettingsDialog.uiTooltip.hardDeleteOperationEnabled=Select Bulk API or Bulk v2 API to enable Hard Delete +AdvancedSettingsDialog.TooltipPropertyName=Property name in config.properties file: {0} +InsertWizard.windowTitle=Load Inserts InsertWizard.confFirstLine=You have chosen to insert new records. Click Yes to begin. +InsertWizard.confSecondLine=Do you want to proceed? + insert.UIAction.menuText=&Insert@Ctrl+I insert.UIAction.tooltipText=Insert Records -InsertWizard.confSecondLine=Do you want to proceed? -TitleDialog.title=Welcome to ${project.name} ${project.version} + +TitleDialog.title=${project.name} ${project.version} TitleDialog.messageLineOne=Welcome to ${project.name} TitleDialog.messageLineTwo=Click a button below, or click Cancel to go to the main window. -LoaderWindow.title=Welcome to ${project.name} ${project.version} + +LoaderDownloadDialog.title=${project.name} ${project.version} +LoaderDownloadDialog.messageLineOne=Version {0} is available at {1} + +LoaderWindow.title=${project.name} ${project.version} LoaderWindow.chooseAction=Please choose an action from the menu. -LoadFinishDialog.viewErrors=View Errors -LoadFinishDialog.title=Operation Finished -LoadWizard.errorAction=Error Running Action -LoadWizard.errorValidDirectory=Please select a valid directory for the status output files. If you clicked Back to reach this page, click Next to continue to the next step. -LogoutUIAction.toolTip=Log out from the current session. LoaderWindow.file=File LoaderWindow.view=View LoaderWindow.settings=Settings -LoadWizard.title=Load LoaderWindow.help=Help + +LoadFinishDialog.viewErrors=View Errors +LoadFinishDialog.title=Operation Finished + +LoadWizard.errorAction=Error Running Action +LoadWizard.errorValidFolder=Please select a valid folder for the status output files. \n\nIf you clicked 'Back' button to reach this page, click Next to continue to the next step. + +LogoutUIAction.toolTip=Log out from the current session. + MappingDialog.title=Mapping Dialog -MappingPage.title=Mapping -MappingPage.titleMsg=Step 3: Mapping -MappingPage.name=Name -MappingDialog.matchlabel=Match the Salesforce fields to your columns. +MappingDialog.matchlabel=Match the Salesforce object fields to your CSV column headers. MappingDialog.clearMapping=Clear Mapping -MappingPage.selectExisting=Choose an Existing Map MappingDialog.autoMatch=Auto-Match Fields to Columns -MappingDialog.dragFields=Drag the Salesforce fields down to the column mapping. To remove a mapping, select a row and click Delete. +MappingDialog.dragFields=Drag the Salesforce object fields down to the column mapping. \nTo remove a mapping, select a row and click Delete key on the keyboard. MappingDialog.saveMapping=Save Mapping MappingDialog.errorSave=Error while saving mapping -MappingPage.description=Map your fields (CSV columns) to the Salesforce object. -MappingDialog.fileColumn=File Column Header -MappingDialog.fileName=Name -MappingPage.createEdit=Create or Edit a Map -MappingPage.fileColumn=File Column Header -MappingDialog.sforceName=Name -MappingDialog.sforceLabel=Label -MappingDialog.sforceType=Type -MappingPage.errorLoading=Error loading properties -MappingPage.errorLoadingRelatedObjectInfo=Error loading related object information -MappingPage.currentBelow= Current Field Mapping: -SettingsPage.title=Apex -SettingsPage.titleMsg=Step 1: Log In -SettingsPage.enterUsernamePassword=Log in to the Salesforce org that you’re importing to or exporting from. -SettingsPage.username=Username: -SettingsPage.password=Password: -SettingsPage.isSessionIdLogin=Login with Session Id: -SettingsPage.isOAuthLogin=Login with OAuth: -SettingsPage.OAuthClientId=OAuth Client Id: -SettingsPage.OAuthClientSecret=OAuth Client Secret: -SettingsPage.OAuthServer=OAuth Server: -SettingsPage.sessionId=Session Id: -SettingsPage.instServerUrl=Salesforce Login URL: -SettingsPage.login=Log in -SettingsPage.verifyingLogin=Verifying Salesforce username and password. -SettingsPage.loginSuccessful=Login successful -SettingsPage.invalidLogin=Error: Check your username and password. If you still can’t log in, contact your Salesforce admin. -SettingsPage.proxyError=Check the Data Loader settings for accessing the proxy server. Error from proxy server: {0} -SettingsPage.environment=Environment -SettingsPage.loginDefault=OAuth -SettingsPage.loginStandard=Password Authentication -SettingsPage.loginAdvanced=Advanced -UpsertWizard.title=Load Upserts +MappingDialog.fileColumn=CSV Column Header +MappingDialog.sforceFieldName=Field Name/Relationship +MappingDialog.sforceFieldLabel=Field Label +MappingDialog.sforceFieldType=Field Data Type + +OAuthInBrowser.title=Login from Browser +OAuthInBrowser.authStep1Title=Step 1: +OAuthInBrowser.authStep2Title=Step 2: +OAuthInBrowser.authStep3Title=Step 3: +OAuthInBrowser.authStep1Content= Click on the Login link below to go to the login page in the browser. Alternately, click 'Copy' button to copy the URL and paste it in your browser's address bar. +OAuthInBrowser.authStep2Content= Page to enter the user code is displayed in the browser. Enter {0} in Code field of the displayed page if it is not prefilled and click \'Connect\'. +OAuthInBrowser.authStep3Content= Complete login steps in the browser. This popup will close automatically upon successful login. +OAuthInBrowser.verificationURL=Login link: +OAuthInBrowser.userCode=User Code: +OAuthInBrowser.copyToClipboardButton=Copy +OAuthInBrowser.batchModeMessage1=Copy the URL shown below and paste it in your browser if the browser is not launched automatically. +OAuthInBrowser.batchModeURL= URL to copy: +OAuthInBrowser.batchModeMessage2=Enter {0} in Code field of the displayed page if the code is not prefilled and click 'Connect'. + +UpsertWizard.windowTitle=Load Upserts UpsertWizard.confFirstLine=You have chosen to add new records and/or update existing records. The action you are about to take cannot be undone. -UpdateWizard.title=Load Updates + +UpdateWizard.windowTitle=Load Updates UpdateWizard.confFirstLine=You have chosen to update existing records. The action you are about to take cannot be undone. -update.UIAction.menuText=&Update@Ctrl+U -upsert.UIAction.menuText=U&psert@Ctrl+P + AdvanceSettingsUIAction.menuText=&Settings -UpdateUIAction.tooltipText=Update Records -UpsertUIAction.tooltipText=Upsert Records AdvanceSettingsUIAction.toolTipText=Set advanced options + UpdateWizard.confSecondLine=Are you sure you want to proceed? UpsertWizard.confSecondLine=Are you sure you want to proceed? + ExtractionDataSelectionDialog.title=Extraction Selection -ExtractionDataSelectionPage.title=Data + ExtractionInputDialog.title=Settings -extract.UIAction.tooltipText=Perform an Export -extract_all.UIAction.tooltipText=Perform an Export (including deleted data) ExtractionInputDialog.message=\ \ Modify your settings: -ExtractionInputDialog.querySize=Query request size: +ExtractionInputDialog.exportBatchSize=Export batch size: ExtractionInputDialog.serverURL=Server URL (clear for default): ExtractionInputDialog.compression=Turn off compression (for debug): ExtractionInputDialog.timeout=Timeout (in seconds): -ExtractionSettingsPage.title=Apex -ExtractionSettingsPage.login=Log In -ExtractionSOQLPage.title=SOQL -ExtractionSOQLPage.fields=Fields + +ExtractionFinishDialog.title=Operation Finished + +ExtractionWizard.windowTitle=Export +ExtractionWizard.confSecondLine=Do you want to proceed? +ExtractionWizard.confFirstLine=You have chosen to perform an export. Click Yes to begin. + +ExtractAllWizard.windowTitle=Export All +ExtractAllWizard.confSecondLine=Do you want to proceed? +ExtractAllWizard.confFirstLine=You have chosen to perform an export of all records, including deleted records. Click Yes to begin. + +DataSelectionPage.csv=CSV +DataSelectionPage.hard_delete=You need the Bulk API Hard Delete user permission to permanently delete data. +DataSelectionPage.title=Step 2: Select Salesforce object +DataSelectionPage.description=Select your Salesforce object and your CSV file. +DataSelectionPage.selectObject=Select Salesforce object to import: +DataSelectionPage.showAll=&Show all Salesforce objects +DataSelectionPage.showAllToolTip=If checked, show all standard objects and custom objects for the org. \n\nIf unchecked, show custom objects and a subset of all standard objects: \nAccount, Case, Contact, Event, Lead, Opportunity, Pricebook2, Product2, Task, User +DataSelectionPage.csvMessage=Import from (CSV file): +DataSelectionPage.selectValid=Please select a valid CSV file. +DataSelectionPage.errorClassCast=Class cast in listener +DataSelectionPage.entity=Object: + ExternalIdPage.errorExternalIdRequired=For upsert, the object must have a Salesforce record ID or an external ID. ExternalIdPage.errorExternalIdRequiredTitle=No ID or External ID Found -ExternalIdPage.title=External ID ExternalIdPage.description=Select the field on your object to use for matching. ExternalIdPage.externalIdComboText=Select the field for matching on {0}: ExternalIdPage.externalIdInfoNoExtId=There are no external ID fields defined on the {0} object. The 'Id' field will be used for matching. ExternalIdPage.externalIdInfoExtIdExists=To match on {0}, use either the 'Id' field or an external ID field. -ExternalIdPage.message=Step 2a: Choose your field to use for matching -ForeignKeyExternalIdPage.title=Foreign Key External ID -ForeignKeyExternalIdPage.message=Step 2b: Choose your related objects -ForeignKeyExternalIdPage.description=For each related object, select the external ID field to use for matching. Otherwise, leave the selection blank. -ForeignKeyExternalIdPage.defaultComboText= -ExtractionSOQLPage.operation=Operation -ExtractionFinishDialog.title=Operation Finished -ExtractionSOQLPage.value=Value -extract.UIAction.menuText=&Export@Ctrl+E -extract_all.UIAction.menuText=Export &All@Ctrl+A -ExitUIAction.tooltipText=Exit -ExtractionWizard.title=Export -ExtractAllWizard.title=Export All -ExtractionInputDialog.proxyHost=Proxy Host: -ExtractionInputDialog.proxyPort=Proxy Port: -ExtractionSettingsPage.titleMsg=Step 1: Salesforce Settings -ExtractionSettingsPage.username=Username: -ExtractionSettingsPage.password=Password: -ExtractionSOQLPage.titleMessage=Step 3: Edit your Query -ExtractionSOQLPage.description=Edit the SOQL query for extraction. -ExtractionWizard.confFirstLine=You have chosen to perform an export. Click Yes to begin. -ExtractAllWizard.confFirstLine=You have chosen to perform an export of all records, including deleted records. Click Yes to begin. -ExtractionSOQLPage.chooseFields=Choose the query fields below. -ExtractionSOQLPage.createClauses=Create the where clauses to your query below. +ExternalIdPage.title=Step 2a: Choose your field to use for matching + +ExtractionDataSelectionPage.showAll=&Show all Salesforce objects +ExtractionDataSelectionPage.title=Step 2: Select Salesforce object +ExtractionDataSelectionPage.description=Select your Salesforce object and your target file +ExtractionDataSelectionPage.selectObject=Select Salesforce Object to export: +ExtractionDataSelectionPage.chooseTarget=Export to (CSV file): +ExtractionDataSelectionPage.chooseFile=Browse... +ExtractionDataSelectionPage.initError=The sobject initialization failed. Please try again. +ExtractionDataSelectionPage.extractOutputError=Error opening extraction output {0} +ExtractionDataSelectionPage.defaultFileName=extract.csv +ExtractionDataSelectionDialog.errorValidating=Salesforce error validating object. + +ExtractionLoginPage.title=Step 1: Log In + +ExtractionSOQLPage.fields=Field: +ExtractionSOQLPage.operation=Operation: +ExtractionSOQLPage.value=Value: +ExtractionSOQLPage.title=Step 3: Edit your Query +ExtractionSOQLPage.description=Edit the SOQL query for extraction +ExtractionSOQLPage.chooseFields=Choose the query fields below: +ExtractionSOQLPage.createClauses=Create the where clauses to your query below: ExtractionSOQLPage.addCondition=Add condition -ExtractionWizard.confSecondLine=Do you want to proceed? -ExtractAllWizard.confSecondLine=Do you want to proceed? ExtractionSOQLPage.queryBelowMsg=The generated query will appear below. You may edit it before finishing. -ExtractionSOQLPage.initializeMsg=Initalizing SOQL -ExtractionDataSelectionPage.showAll=&Show all Salesforce objects -ExtractionInputDialog.proxyUsername=Proxy Username: -ExtractionInputDialog.proxyPassword=Proxy Password: +ExtractionSOQLPage.initializeMsg=Initializing SOQL ExtractionSOQLPage.selectAllFields=Select all fields ExtractionSOQLPage.clearAllFields=Clear all fields +ExtractionSOQLPage.clearAllConditions=Clear all conditions + +FinishPage.title=Step 4: Finish +FinishPage.description=Select the folder where your success and error files will be saved. +FinishPage.overwritten= +FinishPage.output=Output +FinishPage.chooseDir=Results Folder: + +ChooseLookupFieldForRelationshipPage.title=Step 2b: (Optional) relate using lookup field +ChooseLookupFieldForRelationshipPage.description=For each related object, select a lookup fields of related objects for relationship fields. Otherwise, leave the selection blank. +ChooseLookupFieldForRelationshipPage.defaultComboText= +ChooseLookupFieldForRelationshipPage.pageMessage=Relationships of {0} are listed below.\nSelect a related object and its lookup field if the CSV refers to the related object using the selected lookup field. +ChooseLookupFieldForRelationshipPage.relationshipHeader=Relationship +ChooseLookupFieldForRelationshipPage.parentObjectHeader=Related Object +ChooseLookupFieldForRelationshipPage.parentLookupFieldHeader=Lookup Field of Related Object + +HardDeleteFinishPage.title=Step 4: Finish +HardDeleteFinishPage.description=Select the folder where your success and error files will be saved. + +MappingPage.title=Step 3: Mapping +MappingPage.fieldName=Salesforce Object Field Name +MappingPage.selectExisting=Choose an Existing Map +MappingPage.description=Map your fields (CSV columns) to the Salesforce object fields. +MappingPage.createEdit=Create or Edit a Map +MappingPage.fileColumn=CSV Column Header +MappingPage.errorLoading=Error loading properties +MappingPage.errorLoadingRelatedObjectInfo=Error loading related object information +MappingPage.currentMapping= Current Field Mappings: +MappingPage.currentMappingFromFile= Current Field Mappings: \n(from {0}) + +LoginPage.title=Step 1: Log In +LoginPage.description=Log in to the Salesforce org that you're importing to or exporting from. +LoginPage.username=Username: +LoginPage.password=Password+Security Token: +LoginPage.TooltipPassword=Enter your password followed by your security token number you received in an email from Salesforce. For example, if your password is 'mypassword', enter mypasswordXXXXXXXXXX where XXXXXXXXXX is your security token. +LoginPage.isSessionIdLogin=Login with Session Id: +LoginPage.isOAuthLogin=Login with OAuth: +LoginPage.OAuthClientId=OAuth Client Id: +LoginPage.OAuthClientSecret=OAuth Client Secret: +LoginPage.OAuthServer=OAuth Server: +LoginPage.sessionId=Session Id: +LoginPage.instServerUrl=Salesforce Login URL: +LoginPage.login=Log in +LoginPage.verifyingLogin=Verifying Salesforce username and password. +LoginPage.loginSuccessful=Login successful +LoginPage.invalidLogin=Error: Unable to successfully complete log in. Contact your Salesforce admin. +LoginPage.invalidLoginUsernamePassword=Error: Check your username and password+Security Token, entered in the form passwordXXXXXXXXXX where XXXXXXXXXX is your security token. If you still can't log in, contact your Salesforce admin. +LoginPage.invalidLoginOAuthBrowser=Error: Unable to complete browser based OAuth login. Contact your Salesforce admin. +LoginPage.invalidLoginOAuth=Error: Unable to complete OAuth login. Contact your Salesforce admin. +LoginPage.proxyError=Check the Data Loader settings for accessing the proxy server. Error from proxy server: {0} +LoginPage.environment=Environment +LoginPage.loginDefault=OAuth +LoginPage.loginOAuthWithSoftToken=OAuth with User Code +LoginPage.loginStandard=Password Authentication +LoginPage.loginAdvanced=Advanced + +ExtractionInputDialog.proxyUsername=Proxy Username: +ExtractionInputDialog.proxyPassword=Proxy Password: + ExtractionDataSelectionDialog.verifyingEntity=Verifying object and constructing base query. ExtractionDataSelectionDialog.success=Initialization succeeded. -ExtractionSettingsPage.verifyingLogin=Verifying Salesforce username and password. -ExtractionSOQLPage.clearAllConditions=Clear all conditions -ExtractionDataSelectionPage.titleMsg=Step 2: Select Data Objects -ExtractionDataSelectionPage.description=Select your Salesforce object and your target file -ExtractionDataSelectionPage.selectSforce=Select Salesforce Object: -ExtractionDataSelectionPage.chooseTarget=Choose a target for extraction: -ExtractionDataSelectionPage.chooseFile=Browse... -ExtractionDataSelectionPage.initError=The sobject initialization failed. Please try again. -ExtractionDataSelectionPage.extractOutputError=Error opening extraction output {0} + ExtractionFinishDialog.viewExtraction=View Extraction -ExtractionSettingsPage.loginSuccessful=Login completed successfully. -ExtractionDataSelectionDialog.errorValidating=Salesforce error validating object. -ExtractionSettingsPage.invalidUsernamePassword=Invalid username or password. -ExtractionDataSelectionPage.defaultFileName=extract.csv -ExtractionSettingsPage.enterUsernamePassword=Enter your Salesforce username and password. -ExtractionFinishPage.title=Finish -ExtractionFinishPage.finishMsg=Step 4: Finish -ExtractionFinishPage.selectDir=Specify the directory where error files will be saved. +ExtractionFinishPage.title=Step 4: Finish +ExtractionFinishPage.description=Select the folder where your success and error files will be saved. + ExitUIAction.menuText=E&xit@Alt+F4 + ViewCSVFileAction.menuText=View &CSV@Ctrl+V ViewCSVFileAction.tooltipText=View the CSV files. + CSVViewerDialog.rowNumber=Row Number CSVViewerDialog.externalMsg=To open the CSV in the associated external program such as Microsoft Excel, click the button below. CSVViewerDialog.externalButton=Open in external program CSVViewerDialog.errorExternal=Error running external program CSVViewerDialog.errorProcessExit=Process exiting with value: + LoadFinishDialog.viewSuccess=View Successes + HelpUIAction.menuText=&About -HelpUIAction.dlgTitle=Help +HelpUIAction.dlgTitle=About ${project.name} v${project.version} HelpUIAction.dlgMsg=${project.name} is an application for the bulk import or export of data. Use it\nto insert, update, delete, or extract Salesforce records. ${project.name} \ncan move data into or out of any Salesforce object.\n\nCopyright (c) 2015, salesforce.com, inc.\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification, are permitted\nprovided that the following conditions are met:\n\nRedistributions of source code must retain the above copyright notice, this list of conditions\nand the following disclaimer.\n\nRedistributions in binary form must reproduce the above copyright notice, this list of conditions\nand the following disclaimer in the documentation and/or other materials provided with the distribution.\n\nNeither the name of salesforce.com, inc. nor the names of its contributors may be used to\nendorse or promote products derived from this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS\nIS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\nCONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\nEXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\nPROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\nPROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\nLIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\nNEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n -HelpUIAction.dlgLinkText=${project.name} Documentation -HelpUIAction.dlgURL=http://www.salesforce.com/us/developer/docs/dataLoader/ -HelpUIAction.msgHeader=${project.name} Help +HelpUIAction.dlgLinkText=${project.name} Documentation +HelpUIAction.dlgURL= +HelpUIAction.msgHeader=${project.name} v${project.version} HelpUIAction.tooltip=About ${project.name} +HelpUIAction.latestVersion=You are running the latest version + LogoutUIAction.text=Logout +update.UIAction.menuText=&Update@Ctrl+U +upsert.UIAction.menuText=U&psert@Ctrl+P +UpdateUIAction.tooltipText=Update Records +UpsertUIAction.tooltipText=Upsert Records +extract.UIAction.tooltipText=Perform an Export +extract.UIAction.menuText=&Export@Ctrl+E +extract_all.UIAction.menuText=Export &All@Ctrl+A +extract_all.UIAction.tooltipText=Perform an Export (including deleted data) +ExitUIAction.tooltipText=Exit + +Operation.currentAPIUsage=Current API usage for the org: {0} +Operation.apiLimit=API Limit for the org: {0} +Operation.apiVersion={0} version: {1} + +MappingDropActionDialog.title=New Mapping: Map {0} to {1} +MappingDropActionDialog.message=CSV Column Header:\t{0} \n\n\tcurrently maps to \n\nSalesforce object fields:\t{1} +MappingDropActionDialog.addAction=Add To Current Mapping +MappingDropActionDialog.replaceAction=Replace Current Mapping + +LoadPage.importBatchSize=Import batch size: + +####################################### +# All properties descriptions go here. +# These include properties set in config.properties, process-conf.xml, +# and command line options +####################################### +AppConfig.property.description.sfdc.deleteWithExternalId=Perform an update operation \ + by referencing an idLookup field on the object such as an external id field. \ + Make sure that the field has unique values to avoid update failure due to \ + duplicate values. Data Loader uses REST +AppConfig.property.description.sfdc.oauth.Production.partner.clientid=Salesforce Connected App id of data loader in Production when performing operations using SOAP API. Details about Connected Apps documented at https://help.salesforce.com/s/articleView?id=sf.connected_app_overview.htm&type=5 +AppConfig.property.description.dataAccess.skipTotalCount= +AppConfig.property.description.dataAccess.readBatchSize=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdcInternal.isSessionIdLogin= +AppConfig.property.description.process.operation=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.process.thread.name= +AppConfig.property.description.loader.hideWelcome= +AppConfig.property.description.sfdc.proxyHost=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.process.useEuropeanDates=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.load.preserveWhitespaceInRichText=Preserve whitespace in RichText fields to import by replacing space characters with ' '. +AppConfig.property.description.loader.csvComma=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.ui.wizard.xoffset=x coordinate of UI wizard's top-left corner on the screen in pixels. +AppConfig.property.description.process.encryptionKeyFile=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.loader.csvTab=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.dataAccess.writeUTF8=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.loader.query.includeBinaryData= +AppConfig.property.description.sfdc.minRetrySleepSecs=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.process.bulk.cacheDataFromDao=Use the data read from CSV or database during import to store success and failure results. +AppConfig.property.description.sfdc.ui.wizard.height=Height of UI wizard pages in pixels. +AppConfig.property.description.loader.ui.showUpgrade=Show upgrade dialog if a newer version is available when launching data loader in UI mode. +AppConfig.property.description.sfdc.extractionSOQL=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.loader.csvOther=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.ui.wizard.closeOnFinish=If set to true, it closes UI Wizard dialog when the operation is completed. Set it to false if you want to perform the same operation multiple times. +AppConfig.property.description.sfdc.endpoint.Sandbox=See the description of Setting "Server host" at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.proxyPort=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.useBulkApi=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.process.mappingFile=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.dataAccess.name=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.ui.wizard.finishStep.prepopulateWithPreviousOpResultsFolder= +AppConfig.property.description.sfdc.externalIdField=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.system.proxyPort=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.updateWithExternalId=Perform update operation using an idLookup field such as an external id field on a standard sobject or name field of a custom sobject. Make sure that the field values are unique to avoid duplicate record errors. +AppConfig.property.description.sfdc.oauth.Sandbox.bulk.clientid=Salesforce Connected App id of data loader in Sandbox when performing operations using Bulk API or Bulk v2 API. Details about Connected Apps documented at https://help.salesforce.com/s/articleView?id=sf.connected_app_overview.htm&type=5 +AppConfig.property.description.process.enableLastRunOutput=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.bulkApiZipContent=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.resetUrlOnLogin=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.enableRetries=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.oauth.Production.bulk.clientid=Salesforce Connected App id of data loader in Production when performing operations using Bulk API or Bulk v2 API. Details about Connected Apps documented at https://help.salesforce.com/s/articleView?id=sf.connected_app_overview.htm&type=5 +AppConfig.property.description.process.outputError=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.process.lastRunOutputDirectory=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.loader.csvOtherValue=See "Allow other characters as CSV delimiters" at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.salesforce.installation.shortcut.macos.appsfolder=Specify this command line option (values are "true" or "false") to install data loader without prompting the user about creating a symlink to data loader executable from Applications folder on MacOS. +AppConfig.property.description.sfdc.debugMessagesFile=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.assignmentRule=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.process.loadRowToStartAt=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.timeoutSecs=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.salesforce.installation.shortcut.windows.startmenu=Specify this command line option (values are "true" or "false") to install data loader without prompting the user about creating a link to data loader executable from StartMenu on Windows. +AppConfig.property.description.sfdc.bulkApiSerialMode=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.loader.bufferUnprocessedBulkQueryResults=Save server results of an export made using Bulk v2 API in a temporary file before doing any client-side processing on results to minimize the chances of connection disruption resulting in failure to retrieve results even though the export operation succeeds. +AppConfig.property.description.sfdc.loadBatchSize=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.salesforce.installation.shortcut.desktop=Specify this command line option (values are "true" or "false") to install data loader without prompting the user about creating a shortcut in Desktop folder. +AppConfig.property.description.process.batchMode.exitWithErrorOnFailedRows=Exit with error code 4 in batch mode if set to true when the operation is successful but results contain one or more rows with an error. +AppConfig.property.description.process.enableExtractStatusOutput=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.salesforce.saveAllSettings=Save all Settings, including default ones, in config.properties file, if set to true. Save only the settings that have values different from their defaults if set to false. +AppConfig.property.description.loader.query.limitOutputToQueryFields= +AppConfig.property.description.sfdc.system.proxyHost=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.endpoint.Production=See the description of Setting "Server host" at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.endpoint=Legacy property, superseded environment-specific properties sfdc.endpoint.Production and \ + and sfdc.endpoint.Sandbox. See the description of Setting "Server host" at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.oauth.loginfrombrowser= +AppConfig.property.description.sfdc.connectionTimeoutSecs=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.insertNulls=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.process.initialLastRunDate=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.salesforce.api.version=Specify the server API version to use. The default is to use the same version as data loader's version and failing that, use previous version. For example, if data loader version is v62.0.2, it will use API version 62.0. +AppConfig.property.description.process.name=If process bean id is not specified when launching in batch mode, the value \ + of the property process.name in config.properties will be used to run the process instead of process-conf.xml,\ + for example: "process ../myconfigdir" +AppConfig.property.description.datefield.usegmt=Override timezone specified in sfdc.timezone property by using GMT timezone for Date and Datetime fields in an upload operation. +AppConfig.property.description.sfdc.oauth.Production.clientsecret=Specify encrypted OAuth 2.0 client secret for Production. +AppConfig.property.description.sfdc.oauth.Sandbox.clientsecret=Specify encrypted OAuth 2.0 client secret for Sandbox. +AppConfig.property.description.sfdc.proxyNtlmDomain=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.extractionRequestSize=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.extraction.allCapsHeaders=set it to "true" to output extraction field headers in capital letters (uppercase characters). +AppConfig.property.description.sfdc.extraction.outputByteOrderMark=set to "true" by default. When set to "true", it writes Byte Order Mark (BOM) character if the CSV file is created in UTF-8 format. +AppConfig.property.description.config.properties.readonly=Do not modify config.properties file even if the user makes changes through Settings dialog. +AppConfig.property.description.dataAccess.readCharset=Override system default charset by specifying charset to use for import operations. Set it to UTF-8, UTF-16BE, UTF-16LE, UTF-32BE, or UTF-32LE to handle import CSVs with Byte Order Mark (BOM) character. +AppConfig.property.description.dataAccess.writeCharset=Override system default charset by specifying charset to use for export operations. Set it to UTF-8 or UTF-16 to write export CSVs with Byte Order Mark (BOM) character. +AppConfig.property.description.loader.cacheSObjectNamesAndField=Cache object names and fields metadata across multiple operations. Applicable in the UI mode because batch mode executes one operation and stops. +AppConfig.property.description.sfdc.timezone=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.process.outputSuccess=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.password=Specify encrypted password+security token pair. Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.dataAccess.writeBatchSize=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdx.richtext.regex=Regular expression to identify HTML tags in RichText fields so that whitespace characters inside HTML tags are not escaped using " ". +AppConfig.property.description.sfdc.ui.wizard.width=Width of UI wizard pages in pixels. +AppConfig.property.description.sfdc.maxRetries=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.reuseClientConnection=Keep the client connection open across multiple server requests, thereby increasing operation speed. +AppConfig.property.description.run.mode=Possible values are "ui", "batch", "install", "encrypt". +AppConfig.property.description.sfdc.proxyUsername=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.bulkApiCheckStatusInterval=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.useBulkV2Api=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.sfdc.ui.wizard.yoffset=y coordinate of UI wizard's top-left corner on the screen in pixels. +AppConfig.property.description.sfdc.truncateFields=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.loader.query.delimiter= +AppConfig.property.description.dataAccess.type=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.sortExtractionFields= +AppConfig.property.description.sfdc.username=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.process.statusOutputDirectory=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.proxyPassword=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/configuring_the_data_loader.htm +AppConfig.property.description.process.bulk.saveServerLoadAndRawResultsInCSV=Save upload data after mapping CSV column headers to Salesforce fields and upload results before mapping them to import CSV column headers. +AppConfig.property.description.sfdc.wireOutput=Print request and response xml message on console when set to true. +AppConfig.property.description.sfdc.useSysPropsForHttpClient=Setting it to false will switch to legacy behavior (v42.0.0 or earlier). Setting it to true (default) is necessary for mTLS support using a client cert. +AppConfig.property.description.sfdc.noCompression=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.oauth.Sandbox.partner.clientid=Salesforce Connected App id of data loader in Sandbox when performing operations using SOAP API. Details about Connected Apps documented at https://help.salesforce.com/s/articleView?id=sf.connected_app_overview.htm&type=5 +AppConfig.property.description.sfdc.entity=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.salesforce.config.dir=Folder path of the folder containing configuration files such as config.properties, log4j2.properties, process-conf.xml +AppConfig.property.description.dataAccess.readUTF8=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.debugMessages=Details documented at https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/loader_params.htm +AppConfig.property.description.sfdc.formatPhoneFields= +AppConfig.property.description.sfdc.oauth.Production.redirecturi=OAuth redirect URI for Production environment +AppConfig.property.description.sfdc.oauth.Sandbox.redirecturi=OAuth redirect URI for Sandbox environment +AppConfig.property.description.process.keepAccountTeam= +AppConfig.property.description.salesforce.loader.generatePropsCSV=Generate properties.csv file in configs folder containing all properties and command line options. Settings are a subset of properties that are shown in Settings dialog. \ No newline at end of file diff --git a/src/main/resources/log4j.xml b/src/main/resources/log4j.xml deleted file mode 100644 index 0b3ff28e0..000000000 --- a/src/main/resources/log4j.xml +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/log4j2.properties b/src/main/resources/log4j2.properties new file mode 100644 index 000000000..4292455c3 --- /dev/null +++ b/src/main/resources/log4j2.properties @@ -0,0 +1,26 @@ +name = ${project.name} +monitorInterval = 5 +property.basePath = ${sys:java.io.tmpdir} +# RollingFileAppender name, pattern, path and rollover policy +appender.rolling.type = RollingFile +appender.rolling.name = fileAppender +appender.rolling.fileName= ${basePath}/sdl.log +appender.rolling.filePattern= ${basePath}/sdl-%d{yyyy-MM-dd}.log +appender.rolling.layout.type = PatternLayout +appender.rolling.layout.pattern = %d %-5p [%t] %C{2} %M (%F:%L) - %m%n +appender.rolling.policies.type = Policies + +# CONSOLE Appender +appender.console.type = Console +appender.console.name = STDOUT +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d %-5p [%t] %C{2} %M (%F:%L) - %m%n + +# RollingFileAppender rotation policy +appender.rolling.policies.size.type = SizeBasedTriggeringPolicy +appender.rolling.policies.size.size = 100KB + +# Configure root logger +rootLogger.level = info +rootLogger.appenderRef.rolling.ref = fileAppender +rootLogger.appenderRef.stdout.ref = STDOUT \ No newline at end of file diff --git a/src/main/resources/mac/dataloader.app/Contents/Info.plist b/src/main/resources/mac/dataloader.app/Contents/Info.plist new file mode 100644 index 000000000..53c8df7e1 --- /dev/null +++ b/src/main/resources/mac/dataloader.app/Contents/Info.plist @@ -0,0 +1,8 @@ + + + + + CFBundleIconFile + dataloader.icns + + diff --git a/src/main/resources/mac/dataloader.app/Contents/PkgInfo b/src/main/resources/mac/dataloader.app/Contents/PkgInfo new file mode 100644 index 000000000..bd04210fb --- /dev/null +++ b/src/main/resources/mac/dataloader.app/Contents/PkgInfo @@ -0,0 +1 @@ +APPL???? \ No newline at end of file diff --git a/src/main/resources/mac/dataloader.app/Contents/Resources/dataloader.icns b/src/main/resources/mac/dataloader.app/Contents/Resources/dataloader.icns new file mode 100644 index 000000000..d9719f6fe Binary files /dev/null and b/src/main/resources/mac/dataloader.app/Contents/Resources/dataloader.icns differ diff --git a/src/main/resources/mac/dataloader_console b/src/main/resources/mac/dataloader_console new file mode 100755 index 000000000..7d708134f --- /dev/null +++ b/src/main/resources/mac/dataloader_console @@ -0,0 +1,5 @@ +#!/bin/bash +DL_INSTALL_ROOT="$(dirname "$(readlink -f "$0")")" +source "${DL_INSTALL_ROOT}/util/util.sh" + +runDataLoader $@ \ No newline at end of file diff --git a/src/main/resources/mac/encrypt.sh b/src/main/resources/mac/encrypt.sh new file mode 100644 index 000000000..b9c4fd391 --- /dev/null +++ b/src/main/resources/mac/encrypt.sh @@ -0,0 +1,5 @@ +#!/bin/bash +DL_INSTALL_ROOT="$(dirname "$(readlink -f "$0")")" +source ${DL_INSTALL_ROOT}/util/util.sh + +runDataLoader $@ run.mode=encrypt \ No newline at end of file diff --git a/src/main/resources/mac/util/util.sh b/src/main/resources/mac/util/util.sh new file mode 100644 index 000000000..3e367b421 --- /dev/null +++ b/src/main/resources/mac/util/util.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +include () { + [[ -f "$1" ]] && source "$1" +} + +initVars() { + DATALOADER_VERSION="@@FULL_VERSION@@" + DATALOADER_SHORT_VERSION=$(echo ${DATALOADER_VERSION} | cut -d'.' -f 1) + MIN_JAVA_VERSION=@@MIN_JAVA_VERSION@@ + include ${HOME}/.profile + include ${HOME}/.bash_profile + include ${HOME}/.bashrc + include ${HOME}/.zsh_profile + include ${HOME}/.zshrc +} + +checkJavaVersion() { + initVars + + echo "Data Loader requires Java JRE ${MIN_JAVA_VERSION} or later. Checking if it is installed..." + if [ -n "${DATALOADER_JAVA_HOME}" ] + then + JAVA_HOME=${DATALOADER_JAVA_HOME} + fi + + PATH=${JAVA_HOME}/bin:${PATH} + JAVA_VERSION=$(java -version 2>&1 | grep -i version | cut -d'"' -f 2 | cut -d'.' -f 1) + if [ -z "${JAVA_VERSION}" ] + then + echo "Did not find java command." + echo "" + exitWithJavaDownloadMessage + fi + if [ ${JAVA_VERSION} \< ${MIN_JAVA_VERSION} ] + then + echo "Found Java JRE version ${JAVA_VERSION} whereas Data Loader requires Java JRE ${MIN_JAVA_VERSION} or later." + exitWithJavaDownloadMessage + fi +} + +exitWithJavaDownloadMessage() { + echo "Java JRE ${MIN_JAVA_VERSION} or later is not installed or DATALOADER_JAVA_HOME environment variable is not set." + echo "For example, download and install Zulu JRE ${MIN_JAVA_VERSION} or later from here:" + echo " https://www.azul.com/downloads/" + exit 1 +} + +runDataLoader() { + checkJavaVersion + SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + java -cp "${SCRIPT_DIR}/../*" com.salesforce.dataloader.process.DataLoaderRunner "$@" +} \ No newline at end of file diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 4d4e3017a..34195f212 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -1,10 +1,12 @@ -Config.fileMissing=Configuration filename is not specified -Config.errorPropertiesLoad=Error loading configuration from properties file: {0} -Config.errorParameterLoad=Error loading parameter: {0} of type: {1} -Config.errorParameterSave=Error saving parameter: {0} of type: {1} -Config.errorSecurityInit=Error initializing encryption for key file {0}: {1} -Config.errorEncryptionKeyFileInit=Error loading encryption key file {0}: {1} -Config.errorNoRequiredParameter=Required parameter {0} is unspecified or empty +AppConfig.fileMissing=Configuration filename is not specified +AppConfig.errorPropertiesLoad=Error loading configuration from properties file: {0} +AppConfig.errorParameterLoad=Error loading parameter: {0} of type: {1} +AppConfig.errorParameterSave=Error saving parameter: {0} of type: {1} +AppConfig.errorSecurityInit=Error initializing encryption for key file {0}: {1} +AppConfig.errorEncryptionKeyFileInit=Error loading encryption key file {0}: {1} +AppConfig.errorNoRequiredParameter=Required parameter {0} is unspecified or empty +AppConfig.configInit=The controller config has been initialized +AppConfig.errorCreatingOutputDir=Error creating output folder: {0} LastRun.fileMissing=Last run filename is not specified LastRun.errorLoading=Error loading last run file {0}: {1} LastRun.fileInfo=Last run info will be saved in file: {0} @@ -12,12 +14,6 @@ Process.initializingEngine=Initializing process engine Process.settingFieldTypes=Setting field types Process.settingReferenceTypes=Setting object reference types Process.versionMessage=Process engine is only compatible with Java version: {0} and up -Process.help1=Help for Apex Data Loader Process Runner -Process.help2=To use, specify a configuration directory by setting -Dsalesforce.config.dir as a java parameter -Process.help3=Also, you can specify process name from process-conf.xml, as process.name=AccountInsert -Process.help4=For instance 'java -classpath DataLoader.jar com.salesforce.dataloader.process.ProcessRunner process.name=AccountInsert' -Process.help5=If process.name is not specified, config.properties will be used with optionally set individual paramenters as arguments to the ProcessRunner class -Process.help6=For instance 'java -classpath DataLoader.jar com.salesforce.dataloader.process.ProcessRunner process.operation=insert sfdc.loadBatchSize=200' Process.loginError=Unable to log in. Exiting. Process.loadingParameters=Loading parameters Process.loggingIn=Logging in to: {0} @@ -29,15 +25,15 @@ Process.aboutPerform=About to perform: {0} Process.missingRequiredArg=Missing required argument: {0} Action.loading=Loading: {0} Action.logCanceled=Load was canceled. -Action.exception=Exception occured during loading +Action.exception=Exception occurred during loading Action.logSuccess=Load completed successfully. Action.success=The operation has fully completed. There were {0} successful {1}s and {2} errors. AbstractExtractAction.success=The operation has fully completed. There were {0} successful extractions and {2} errors. Action.cancel=The operation was cancelled. There were {0} successful {1}s and {2} errors. Action.error=Load finished with an error. Please check the log. -Action.errorDaoStartRow=Can't read from dao when calulating start row +Action.errorDaoStartRow=Can't read from DAO when calculating start row Action.errorLastRun=Can't save last run info -RowToStartUtil.errorDaoStartRow=Can't read from dao when calculating start row +RowToStartUtil.errorDaoStartRow=Can't read from DAO when calculating start row RowToStartUtil.errorLastRun=Can't save last run info Action.errorOpeningErrorFile=Error opening error status file: {0} Action.errorOpeningSuccessFile=Error opening success status file: {0} @@ -47,6 +43,7 @@ BulkLoadAction.loading=Loading Using Bulk API: {0} BulkLoadVisitor.noFieldVal=No value provided for field: {0} BulkApiVisitorUtil.logJobCreated=Created Bulk API Job: {0} BulkLoadVisitor.logBatchInfoWithMessage=BatchInfo: {0} - {1}. State Message: "{2}" +BulkLoadVisitor.noResultForRow=BatchInfo: Did not find result for row {0} in batch {1}. BulkLoadVisitor.retrievingResults=Retrieving Bulk Job Results BulkLoadVisitor.errorOutOfSync=Local and remote rows out of sync. Result files are invalid. BulkLoadVisitor.batchError=Not processed due to batch error: {0} @@ -57,7 +54,7 @@ JobState.debugSaveBatch=Saving active batch: {0} BulkApiVisitorUtil.logJobStatus=Batch Status: {0} Queued, {1} In Progress, {2} Completed, {3} Failed. RowUtil.error=Error Calculating Total Rows RowUtil.warningDuplicateColumn=A duplicate column [{0}] is found -RowUtil.warningEmptyColumn=An empty column is found +RowUtil.warningEmptyColumn=An empty column is found in the header row AbstractExtractAction.errorOpeningErrorFile=Error opening error file: {0} AbstractExtractAction.errorOpeningSuccessFile=Error opening success file: {0} AbstractExtractAction.errorMissingErrorFile=Error initializing error status file: empty filename is specified. @@ -68,15 +65,19 @@ AbstractExtractAction.errorMissingFrom=Error in query: missing [ from ]: {0} AbstractExtractAction.errorInvalidFieldName=Error in query: invalid field name [{0}] in query string: {1} AbstractExtractAction.errorMalformedQuery=Error in query: Malformed query string: {0} AbstractExtractAction.errorEmptyQuery=Error in query: query is empty -LoadRateCalculator.processed=Processed {0} of {1} total records. Rate: {2} records per hour. Estimated time to complete: {3} minutes and {4} seconds. There are {5} successes and {6} errors. -LoadRateCalculator.processedTimeUnknown=Processed {0} total records. Rate: {1} records per hour. There are {2} successes and {3} errors. +LoadRateCalculator.processed=Processed {0} of {1} records in {8} minutes, {7} seconds with {5} successes and {6} errors. \nRate: {2} records per hour. Estimated remaining time to complete: {3} minutes and {4} seconds. +LoadRateCalculator.processedTimeUnknown=Processed {0} of {1} records with {2} successes and {3} errors. Visitor.emptyRowIgnored=Item #: {0} will not be loaded due to the empty input data Visitor.conversionException=Conversion Exception, writing to errors.csv Visitor.conversionErrorMsg=Error converting value to correct data type: {0} +Visitor.insufficientAccessToContentMsg=Your system security settings are preventing Data Loader from accessing content file. \nEither move the content file to another folder or consult your system administrator for further assistance. Visitor.dynaBeanError=Dyna Bean error Visitor.invocationError=Invocation Target error Visitor.noErrorReceivedMsg=NO ERROR MESSAGE RECEIVED Visitor.errorResultsLength=Results length must be the same as the data. Stopping output writer. +FileByteArrayConverter.insufficientAccessToContentGenericMsg=Your system security settings are preventing Data Loader from accessing content file {0}. \n Either move the file to another folder or contact your systems administrator for further assistance. +FileByteArrayConverter.insufficientAccessToContentOnMacMsg1=Insufficient access privileges for {0} +FileByteArrayConverter.insufficientAccessToContentOnMacMsg2=Modify System Settings to enable Files and Folders access to Terminal app for Desktop and Downloads folders. \nAlternately, move the file to a folder other than Desktop or Downloads. SforceDynaBean.errorCreatingDynaBean=Error creating DynaBean for salesforce object {0} AbstractQueryVisitor.noneReturned=No Records Returned AbstractQueryVisitor.extracting=Extracting @@ -91,10 +92,12 @@ Client.targetError=Error in Target text, check the value. Client.bindingError=Error creating binding to soap service, error was: {0} Client.sforceLogin=Beginning Partner Salesforce login .... PartnerClient.sforceLoginDetail=Salesforce login to {0} as user {1} -Client.sforceLoginProxyDetail=Salesforce login will use proxy host: {0} port: {1} -Client.sforceLoginProxyUser=Salesforce login will use proxy user: {0} -Client.sforceLoginProxyPassword=Salesforce login will use proxy password from settings -Client.sforceLoginProxyNtlm=Salesforce login will use proxy NTLM domain: {0} +PartnerClient.failedUsernamePasswordAuth=Failed to authenticate using the authentication service URL {0} specified for {1} environment. Error: {2} +PartnerClient.retryUsernamePasswordAuth=Retrying to authenticate using username/password server URL {0} by setting the property {1} +AppUtil.sforceLoginProxyDetail=Calls to Salesforce service will use proxy host: {0} port: {1} +AppUtil.sforceLoginProxyUser=Calls to Salesforce service will use proxy user: {0} +AppUtil.sforceLoginProxyPassword=Calls to Salesforce service will use proxy password from settings +AppUtil.sforceLoginProxyNtlm=Calls to Salesforce service will use proxy NTLM domain: {0} Client.fieldsError=Error getting Fields Client.arraySize=Object array size to be loaded: Client.resultNull=Save Result Null @@ -124,17 +127,14 @@ Controller.errorConfigWritableDmg=Data Loader must be installed in the Applicati Controller.errorConfigSave=Error saving config file: {0}. Please make sure that it is writable. Controller.errorConfigLoad=Error loading config file: {0}. Please make sure that it exists and is readable Controller.errorOperationNotSupported=Requested operation: {0} is not supported. Please check your configuration. Supported operations are: insert, update, upsert, delete, extract. -Controller.logInit=The log has been initialized -Controller.configInit=The controller config has been initialized +AppUtil.logInit=The log has been initialized +AppUtil.processExecutionError=Error running external program Controller.errorLogInit=Logger Configuration failed. Exiting Application Controller.errorDatabaseNotSupportedInTheUI=Error getting database configuration. The database connectivity is only supported in command line version. Controller.errorDAOCreate=Error creating data access object Controller.errorOperationInfoLoad=Error loading custom action configuration. Custom actions will not be available for execution. Controller.errorDaoFactoryLoad=Error loading custom data access object configuration. Custom data access objects will not be available for execution. -Controller.loadingFactoryConfig=Loading action configuration from config file: {0} -Controller.errorLoadingFactoryConfig=Error loading: {0} configuration from the configuration file: {1} -Controller.errorCreatingOutputDir=Error creating output directory: {0} -Controller.invalidOutputDir=Invalid output directory specified: {0}. Please make sure that the directory exists and has a write permission. +Controller.invalidOutputDir=Invalid output folder specified: {0}. Please make sure that the folder exists and has a write permission. Controller.errorFileCreate=Error creating file: {0} Controller.errorFileWrite=Error writing to file: {0}. Please make sure that the file has a write permission. Controller.errorSameFile=Data access CSV: {0} is the same as the output file: {1} @@ -147,6 +147,7 @@ Mapper.errorFileName=File name not specified DAOLoadVisitor.statusItemCreated=Item Created DAOLoadVisitor.statusItemUpdated=Item Updated DAOLoadVisitor.statusItemDeleted=Item Deleted +DAOLoadVisitor.statusItemUndeleted=Item Undeleted BulkLoadVisitor.statusItemHardDeleted=Item Hard Deleted Action.errorWrongDao=Wrong type of data access object encountered: {0} expecting: {1} for operation: {2} DatabaseDAO.errorConfigFileExists=Error loading database configuration file {0}: make sure it exists and is readable @@ -175,17 +176,88 @@ CSVFileDAO.errorUnsupportedEncoding=Unsupported Encoding. Open operation failed CSVFileDAO.errorHeaderRow=Error getting header row from the CSV file. CSVFileDAO.errorOpenNoHeaderRow=Error opening CSV file for writing: header row (with column names) has to be provided CSVFileDAO.errorInitializing=Initialization of CSV FAILED. -CSVFileDAO.errorRowTooLarge=Error reading row #{0}: the number of data columns ({1}) exceeds the number of columns in the header ({2}) +CSVFileDAO.errorRowTooLarge=Error reading data row #{0}: the number of columns ({1}) exceeds the number of columns in the header ({2}) +CSVFileDAO.errorRowTooSmall=Error reading data row #{0}: the number of columns ({1}) is less than the number of columns in the header ({2}) +CSVFileDAO.debugMessageRowSize=Data row #{0}: number of columns = {1} +CSVFileDAO.debugMessageHeaderRowSize=Header row: number of columns = {0} +CSVFileDAO.debugMessageCommaSeparator=comma is a CSV delimiter character +CSVFileDAO.debugMessageTabSeparator=tab is a CSV delimiter character +CSVFileDAO.debugMessageSeparatorChar=CSV delimiter character:"{0}" + ProcessConfig.loadingConfig=Loading process configuration from config file: {0} ProcessConfig.errorNoProcess=Error loading process: {0} configuration from config file: {1} + OperationInfo.loadingConfig=Loading action configuration from config file: {0} OperationInfo.errorOperationInstantiation=Error instantiating operation {0}: could not instantiate class: {1}. OperationInfo.createAction=Instantiating action for operation: {0} -OperationInfo.creatingWizard=Instantiating wizard: {0} + +OperationInfoUIHelper.creatingWizard=Instantiating wizard: {0} +OperationInfoUIHelper.errorOperationInstantiation=Error instantiating operation {0}: could not instantiate class: {1}. + DataAccessObjectFactory.errorDaoInstantiation=Error instantiating data access object {0}: error getting class: {1}. Please make sure a correct class is defined and accessible for the data access object. DataAccessObjectFactory.errorDaoNoConstructor=Error instantiating data access object {0}: constructor: {1} has to be implemented DataAccessObjectFactory.errorInterfaceNotImplemented=Error instantiating data access object {0}: interface DataAccessObject has to be implemented DataAccessObjectFactory.errorDaoConstructorCall=Error instantiating data access object {0} using constructor: {1} DataAccessObjectFactory.daoTypeNotSupported=The specified data access object type: {0} is not supported DataAccessObjectFactory.creatingDao=Instantiating data access object: {0} of type: {1} -FinishPage.cannotMapBase64ForBulkApi=Data Loader cannot map "{0}" field using Bulk API and CSV content type. Please enable the ZIP_CSV content type for Bulk API. \ No newline at end of file +FinishPage.cannotMapBase64ForBulkApi=Data Loader cannot map "{0}" field.\nUse SOAP API or Bulk API and enable \'Import binary content (as Zip File)\' setting in Settings dialog. + +Installer.initialMessage= Data Loader installation requires you to provide an installation folder\n\ +to create a version-specific child folder for the installation artifacts.\n\ +It uses "{0}" as the installation folder if you provide \n\ +a relative path for the installation folder. + +Installer.installationDirConfirmation=Data Loader v{0} will be installed in {1} +Installer.overwriteInstallationDirPrompt=Do you want to overwrite previously installed Data Loader\n\ +v{0} and its configuration in {1}?\n\n\ +If not, installation will quit and you can restart installation using\n\ +another folder. [Yes/No] +Installer.deletionInProgressMessage=Deleting existing Data Loader v{0}... +Installer.promptAnswerYes=Yes +Installer.promptAnswerNo=No +Installer.reprompt=Type Yes or No. +Installer.responseReadError=Can't read your response. Try again. +Installer.shortcutCreateError=Unable to create shortcut +Installer.createDesktopShortcutPrompt=Do you want to create a Desktop shortcut? [Yes/No] +Installer.successCreateDesktopShortcut=Desktop shortcut created. +Installer.createApplicationsDirShortcutPrompt=Do you want to create a shortcut in Applications folder? [Yes/No] +Installer.successCreateApplicationsDirShortcut=A shortcut in Applications folder created. +Installer.createStartMenuShortcutPrompt=Do you want to create a Start menu shortcut? [Yes/No] +Installer.successCreateStartMenuShortcut=Start menu shortcut created. +Installer.exitMessage=Data Loader installation is complete. Press key to continue ... +Installer.promptInstallationFolder=Provide the installation folder [default: dataloader] : +Installer.promptCurrentInstallationFolder=Do you want to install Data Loader in the current folder ({0})? [Yes/No] + +AppUtil.banner=\ +\n\ +*************************************************************************\n\ +** **\n\ +** Salesforce Data Loader **\n\ +** ====================== **\n\ +** **\n\ +** Data Loader v{0} is a Salesforce supported Open Source project to **\n\ +** help you import data to and export data from your Salesforce org. **\n\ +** It requires Java JRE {1} or later to run. **\n\ +** **\n\ +** Github Project Url: **\n\ +** https://github.com/forcedotcom/dataloader **\n\ +** Salesforce Documentation: **\n\ +** https://help.salesforce.com/articleView?id=data_loader.htm **\n\ +** **\n\ +*************************************************************************\n\ +\n + +OAuthBrowserLoginRunner.failedAuthStart=Failed to authenticate using OAuth server URL {0}. Error: {1} +OAuthBrowserLoginRunner.retryAuthStart=Retrying to authenticate using OAuth server URL {0} + +ConfigPropertyMetadata.errorGeneratePathToCSV=Cannot provide full path to the CSV containing information about configurable properties of Data Loader +ConfigPropertyMetadata.errorGenerateCSV=Cannot output CSV containing information about configurable properties of Data Loader +ConfigPropertyMetadata.errorOutputPropInfo=Unable to output information about property: {0} +ConfigPropertyMetadata.infoGeneratedCSVLocation=CSV file containing information about Data Loader configurable properties is generated at {0} +ConfigPropertyMetadata.csvHeader.COL_PROPERTY_NAME=Property +ConfigPropertyMetadata.csvHeader.COL_DESCRIPTION=Description +ConfigPropertyMetadata.csvHeader.COL_UI_LABEL=Property label in Settings dialog +ConfigPropertyMetadata.csvHeader.COL_DEFAULT_VAL=Default value +ConfigPropertyMetadata.csvHeader.COL_IS_READ_ONLY=Is read-only? +ConfigPropertyMetadata.csvHeader.COL_IS_COMMAND_LINE_OPTION=Is settable only as a Command line option? +ConfigPropertyMetadata.csvHeader.COL_IS_ENCRYPTED=Is encrypted? \ No newline at end of file diff --git a/src/main/nsis/samples/conf/accountExtractMap.sdl b/src/main/resources/samples/conf/accountExtractMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/accountExtractMap.sdl rename to src/main/resources/samples/conf/accountExtractMap.sdl diff --git a/src/main/nsis/samples/conf/accountExtractToDbMap.sdl b/src/main/resources/samples/conf/accountExtractToDbMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/accountExtractToDbMap.sdl rename to src/main/resources/samples/conf/accountExtractToDbMap.sdl diff --git a/src/main/nsis/samples/conf/accountInsertMap.sdl b/src/main/resources/samples/conf/accountInsertMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/accountInsertMap.sdl rename to src/main/resources/samples/conf/accountInsertMap.sdl diff --git a/src/main/nsis/samples/conf/accountMasterMap.sdl b/src/main/resources/samples/conf/accountMasterMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/accountMasterMap.sdl rename to src/main/resources/samples/conf/accountMasterMap.sdl diff --git a/src/main/nsis/samples/conf/accountUpdateMap.sdl b/src/main/resources/samples/conf/accountUpdateMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/accountUpdateMap.sdl rename to src/main/resources/samples/conf/accountUpdateMap.sdl diff --git a/src/main/resources/samples/conf/database-conf.xml b/src/main/resources/samples/conf/database-conf.xml new file mode 100644 index 000000000..2b6538a42 --- /dev/null +++ b/src/main/resources/samples/conf/database-conf.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER + FROM TableOwner.Accounts + + + + + account_name + business_phone + sfdc_account_id + account_ext_id + annual_revenue + last_updated + account_number + + + + + + + SELECT ACCOUNT_NAME, BUSINESS_PHONE, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER + FROM TableOwner.Accounts + WHERE LAST_UPDATED > @process.lastRunDate@ + + + + + account_name + business_phone + account_ext_id + annual_revenue + last_updated + account_number + + + + + + + + + + + + + update TableOwner.Accounts accounts + set accounts.account_name = @account_name@, + accounts.business_phone = @business_phone@, + accounts.sfdc_account_id = @sfdc_account_id@, + accounts.annual_revenue = @annual_revenue@, + accounts.account_number = @account_number@ + where + accounts.ACCOUNT_EXT_ID = @account_ext_id@ + + + + + + + + + + + + + + + + + INSERT INTO TableOwner.Accounts ( + ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ANNUAL_REVENUE, ACCOUNT_EXT_ID, ACCOUNT_NUMBER) + VALUES (@account_name@, @business_phone@, @sfdc_account_id@, @annual_revenue@, @account_ext_id@, @account_number@) + + + + + + + + + + + + + + + + + DELETE FROM TableOwner.Accounts + + + + diff --git a/src/main/nsis/samples/conf/opportunityInsertMap.sdl b/src/main/resources/samples/conf/opportunityInsertMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/opportunityInsertMap.sdl rename to src/main/resources/samples/conf/opportunityInsertMap.sdl diff --git a/src/main/nsis/samples/conf/opportunityUpdateMap.sdl b/src/main/resources/samples/conf/opportunityUpdateMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/opportunityUpdateMap.sdl rename to src/main/resources/samples/conf/opportunityUpdateMap.sdl diff --git a/src/main/nsis/samples/conf/opportunityUpsertMap.sdl b/src/main/resources/samples/conf/opportunityUpsertMap.sdl similarity index 100% rename from src/main/nsis/samples/conf/opportunityUpsertMap.sdl rename to src/main/resources/samples/conf/opportunityUpsertMap.sdl diff --git a/src/main/resources/samples/conf/process-conf.xml b/src/main/resources/samples/conf/process-conf.xml new file mode 100644 index 000000000..6a6235b94 --- /dev/null +++ b/src/main/resources/samples/conf/process-conf.xml @@ -0,0 +1,108 @@ + + + + AccountMaster job gets the Customer record updates from ERP (Oracle financials) and uploads them to salesforce using 'upsert'. + + + + + + + + + + + + + + + + + + + + + + + + Opportunity Upsert job gets the Customer record updates from a CSV file and uploads them to salesforce using 'upsert'. + + + + + + + + + + + + + + + + + + + + + + DatabaseAccountExtract job gets account info from salesforce and updates or inserts info into database." + + + + + + + + + + + + + + + + + + + + + + + + csvAccountExtract job gets account info from salesforce and saves info into a CSV file." + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/nsis/samples/data/accountData.csv b/src/main/resources/samples/data/accountData.csv similarity index 100% rename from src/main/nsis/samples/data/accountData.csv rename to src/main/resources/samples/data/accountData.csv diff --git a/src/main/nsis/samples/data/opportunityData.csv b/src/main/resources/samples/data/opportunityData.csv similarity index 100% rename from src/main/nsis/samples/data/opportunityData.csv rename to src/main/resources/samples/data/opportunityData.csv diff --git a/src/main/nsis/samples/status/accountMasterSoapTrace.log b/src/main/resources/samples/status/accountMasterSoapTrace.log similarity index 100% rename from src/main/nsis/samples/status/accountMasterSoapTrace.log rename to src/main/resources/samples/status/accountMasterSoapTrace.log diff --git a/src/main/nsis/samples/status/error0413060133.csv b/src/main/resources/samples/status/error0413060133.csv similarity index 100% rename from src/main/nsis/samples/status/error0413060133.csv rename to src/main/resources/samples/status/error0413060133.csv diff --git a/src/main/nsis/samples/status/success0413060133.csv b/src/main/resources/samples/status/success0413060133.csv similarity index 100% rename from src/main/nsis/samples/status/success0413060133.csv rename to src/main/resources/samples/status/success0413060133.csv diff --git a/src/main/resources/templates/database-conf.xml b/src/main/resources/templates/database-conf.xml deleted file mode 100644 index 5daf1292a..000000000 --- a/src/main/resources/templates/database-conf.xml +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - SELECT ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER - FROM DATALOADER.INT_ACCOUNTS - ORDER BY ACCOUNT_EXT_ID ASC - - - - - account_name - business_phone - sfdc_account_id - account_ext_id - annual_revenue - last_updated - account_number - - - - - - - SELECT ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER - FROM DATALOADER.INT_ACCOUNTS - WHERE SYSTEM_MODSTAMP > @compare_date@ - - - - - account_name - business_phone - sfdc_account_id - account_ext_id - annual_revenue - last_updated - account_number - - - - - - - - - - - - SELECT ACCOUNT_NAME, BUSINESS_PHONE, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER - FROM DATALOADER.INT_ACCOUNTS - WHERE SYSTEM_MODSTAMP > @process.lastRunDate@ - - - - - account_name - business_phone - account_ext_id - annual_revenue - last_updated - account_number - - - - - - - - - - - - - update DATALOADER.int_accounts accounts - set accounts.account_name = @account_name@, - accounts.business_phone = @business_phone@, - accounts.sfdc_account_id = @sfdc_account_id@, - accounts.annual_revenue = @annual_revenue@, - accounts.account_number = @account_number@, - accounts.last_updated = @last_updated@ - where - accounts.ACCOUNT_EXT_ID = @account_ext_id@ - - - - - - - - - - - - - - - - - - INSERT INTO DATALOADER.INT_ACCOUNTS ( - ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ANNUAL_REVENUE, ACCOUNT_EXT_ID, ACCOUNT_NUMBER, LAST_UPDATED) - VALUES (@account_name@, @business_phone@, @sfdc_account_id@, @annual_revenue@, @account_ext_id@, @account_number@, @last_updated@) - - - - - - - - - - - - - - - - - - DELETE FROM DATALOADER.INT_ACCOUNTS - - - - diff --git a/src/main/resources/templates/log-conf.xml b/src/main/resources/templates/log-conf.xml deleted file mode 100644 index 1ce230472..000000000 --- a/src/main/resources/templates/log-conf.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/templates/process-conf.xml b/src/main/resources/templates/process-conf.xml deleted file mode 100644 index a7dd5a986..000000000 --- a/src/main/resources/templates/process-conf.xml +++ /dev/null @@ -1,127 +0,0 @@ - - - - - maximumBatchRowsDb job gets the Account updates from database and uploads them to salesforce using 'upsert'. - - - - - - - - - - - - - - - - - - upsertAccountDb job gets the Account updates from database and uploads them to salesforce using 'upsert'. - - - - - - - - - - - - - - - - - - - - - - extractAccountDb job gets account info from salesforce and updates or inserts info into database." - - - - - - - - - - - - - - - - - - - extractAccountCsv job gets account info from salesforce and saves info into a CSV file." - - - - - - - - - - - - - - - - updateAccountCsv job gets account info to salesforce and saves from a CSV file." - - - - - - - - - - - - - - - insertNullsDBProcess job gets the Account updates from database and uploads them to salesforce using 'upsert'. - - - - - - - - - - - - - - - - - diff --git a/src/main/resources/win/bin/encrypt.bat b/src/main/resources/win/bin/encrypt.bat new file mode 100644 index 000000000..794ec7b90 --- /dev/null +++ b/src/main/resources/win/bin/encrypt.bat @@ -0,0 +1,2 @@ +@echo off +CALL "%~dp0..\util\util.bat" :runDataLoader %* run.mode=encrypt \ No newline at end of file diff --git a/src/main/resources/win/bin/process.bat b/src/main/resources/win/bin/process.bat new file mode 100644 index 000000000..53d83bfdd --- /dev/null +++ b/src/main/resources/win/bin/process.bat @@ -0,0 +1,2 @@ +@echo off +CALL "%~dp0..\util\util.bat" :runDataLoader %* run.mode=batch \ No newline at end of file diff --git a/src/main/resources/win/dataloader.bat b/src/main/resources/win/dataloader.bat new file mode 100755 index 000000000..b0c04fb35 --- /dev/null +++ b/src/main/resources/win/dataloader.bat @@ -0,0 +1,2 @@ +@echo off +CALL "%~dp0util\util.bat" :runDataLoader %* \ No newline at end of file diff --git a/src/main/resources/win/dataloader.ico b/src/main/resources/win/dataloader.ico new file mode 100755 index 000000000..d786b3c4c Binary files /dev/null and b/src/main/resources/win/dataloader.ico differ diff --git a/src/main/resources/win/util/util.bat b/src/main/resources/win/util/util.bat new file mode 100755 index 000000000..219d8c337 --- /dev/null +++ b/src/main/resources/win/util/util.bat @@ -0,0 +1,74 @@ +@ECHO OFF +REM call the function specified in 1st param and return the errorlevel set by the function +REM +CALL :initVars +CALL %* +EXIT /b %ERRORLEVEL% + +:initVars + SET DATALOADER_VERSION=@@FULL_VERSION@@ + FOR /f "tokens=1 delims=." %%a IN ("%DATALOADER_VERSION%") DO ( + SET DATALOADER_SHORT_VERSION=%%a + ) + SET MIN_JAVA_VERSION=@@MIN_JAVA_VERSION@@ + EXIT /b 0 + +:checkJavaVersion + echo Data Loader requires Java JRE %MIN_JAVA_VERSION% or later. Checking if it is installed... + IF NOT "%DATALOADER_JAVA_HOME%" == "" ( + SET "JAVA_HOME=%DATALOADER_JAVA_HOME%" + ) + SET "PATH=%JAVA_HOME%\bin\;%PATH%;" + + java -version 1>nul 2>nul || ( + echo Did not find java command. + echo. + GOTO :exitWithJavaDownloadMessage + ) + + FOR /f "tokens=3" %%a IN ('java -version 2^>^&1 ^| FINDSTR /i "version"') DO ( + SET JAVA_FULL_VERSION=%%a + ) + SET JAVA_FULL_VERSION=%JAVA_FULL_VERSION:"=% + + FOR /f "tokens=1 delims=." %%m IN ("%JAVA_FULL_VERSION%") DO ( + SET /A JAVA_MAJOR_VERSION=%%m + ) + + IF %JAVA_MAJOR_VERSION% LSS %MIN_JAVA_VERSION% ( + echo Found Java JRE version %JAVA_FULL_VERSION% whereas Data Loader requires Java JRE %MIN_JAVA_VERSION% or later. + GOTO :exitWithJavaDownloadMessage + ) + EXIT /b 0 + +:exitWithJavaDownloadMessage + echo Java JRE %MIN_JAVA_VERSION% or later is not installed or DATALOADER_JAVA_HOME environment variable is not set. + echo For example, download and install Zulu JRE %MIN_JAVA_VERSION% or later from here: + echo https://www.azul.com/downloads/ + echo. + echo After the installation, set DATALOADER_JAVA_HOME environment variable to the value + echo ^ + echo. + EXIT -1 + +:runDataLoader + CALL :checkJavaVersion + java -cp "%~dp0..\*" com.salesforce.dataloader.process.DataLoaderRunner %* + EXIT /b %ERRORLEVEL% + +REM Shortcut files have .lnk extension +:CreateShortcut + powershell -Command "$WshShell = New-Object -comObject WScript.Shell; $Shortcut = $WshShell.CreateShortcut(""""%~1""""); $Shortcut.WorkingDirectory = """"%~2""""; $Shortcut.TargetPath = """"%~2\dataloader.bat""""; $Shortcut.IconLocation = """"%~2\dataloader.ico""""; $Shortcut.WindowStyle=7; $Shortcut.Save()" + EXIT /b 0 + +:createStartMenuShortcut + IF NOT EXIST "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Salesforce\" ( + mkdir "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Salesforce" + ) + CALL :CreateShortcut "%APPDATA%\Microsoft\Windows\Start Menu\Programs\Salesforce\Dataloader %DATALOADER_VERSION%.lnk" "%~1" + EXIT /b 0 + +:createDesktopShortcut + for /f "usebackq tokens=3*" %%D IN (`reg query "HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders" /v Desktop`) do set DESKTOP_DIR=%%D + CALL :CreateShortcut "%DESKTOP_DIR%\Dataloader %DATALOADER_VERSION%.lnk" "%~1" + EXIT /b 0 \ No newline at end of file diff --git a/src/test/java/com/salesforce/dataloader/ConfigTestBase.java b/src/test/java/com/salesforce/dataloader/ConfigTestBase.java index b8d123e5f..68c9ba392 100644 --- a/src/test/java/com/salesforce/dataloader/ConfigTestBase.java +++ b/src/test/java/com/salesforce/dataloader/ConfigTestBase.java @@ -25,9 +25,8 @@ */ package com.salesforce.dataloader; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.exception.ConfigInitializationException; -import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.config.AppConfig; + import org.junit.Before; import java.util.Collections; @@ -42,8 +41,8 @@ public abstract class ConfigTestBase extends TestBase { @Deprecated protected static enum TestProperties { @Deprecated - ENTITY_DEFAULT(Config.ENTITY), @Deprecated - ACCOUNT_EXTID(Config.EXTERNAL_ID_FIELD); + ENTITY_DEFAULT(AppConfig.PROP_ENTITY), @Deprecated + ACCOUNT_EXTID(AppConfig.PROP_IDLOOKUP_FIELD); private final String configName; @@ -65,14 +64,21 @@ public void putConfigSetting(Map destConfig) { } } - private final Map testConfig; + private final Map baseConfig; protected Map getTestConfig() { - final HashMap configBase = new HashMap(this.testConfig); - configBase.put(Config.LAST_RUN_OUTPUT_DIR, getTestStatusDir()); + final HashMap configBase = new HashMap(this.baseConfig); + configBase.put(AppConfig.PROP_LAST_RUN_OUTPUT_DIR, getTestStatusDir()); for (TestProperties prop : getDefaultTestPropertiesSet()) { prop.putConfigSetting(configBase); } + configBase.put(AppConfig.CLI_OPTION_CONFIG_DIR_PROP, TEST_CONF_DIR); + String proxyUsername = System.getProperty(AppConfig.PROP_PROXY_USERNAME); + String proxyPassword = System.getProperty(AppConfig.PROP_PROXY_PASSWORD); + if (proxyUsername != null && proxyPassword != null) { + configBase.put(AppConfig.PROP_PROXY_USERNAME, proxyUsername); + configBase.put(AppConfig.PROP_PROXY_PASSWORD, proxyPassword); + } return configBase; } @@ -91,24 +97,11 @@ protected ConfigTestBase(Map testConfig) { if (testConfig == null) { testConfig = new HashMap(); } - this.testConfig = testConfig; + this.baseConfig = testConfig; } @Before - public void loadParameterOverrides() throws Exception { - getController().getConfig().loadParameterOverrides(getTestConfig()); - } - - @Override - protected void setupController() { - super.setupController(); - try { - getController().getConfig().loadParameterOverrides(getTestConfig()); - } catch (ParameterLoadException e) { - fail(e); - } catch (ConfigInitializationException e) { - fail(e); - } + public void setupController() throws Exception { + super.setupController(getTestConfig()); } - } diff --git a/src/test/java/com/salesforce/dataloader/EncryptionUtil/EncryptionUtilTest.java b/src/test/java/com/salesforce/dataloader/EncryptionUtil/EncryptionUtilTest.java new file mode 100644 index 000000000..d56696a0a --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/EncryptionUtil/EncryptionUtilTest.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.EncryptionUtil; + +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.dao.EncryptedDataSource; +import com.salesforce.dataloader.security.EncryptionAesUtil; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.GeneralSecurityException; + +/** + * com.salesforce.dataloader + * + * @author xbian + */ +public class EncryptionUtilTest extends ConfigTestBase { + + private static final Logger logger = DLLogManager.getLogger(EncryptionUtilTest.class); + + @Test /* test for single line encryption */ + public void testEncryption() throws Exception { + EncryptionAesUtil encryptionAesUtil = new EncryptionAesUtil(); + String passwordText = "somePassword6c3708b3"; + byte[] secureKey = encryptionAesUtil.generateEncryptionKey(); + logger.info("\nEncryption key:" + secureKey.toString()); + byte[] encryptedMsg = encryptionAesUtil.encryptMsg(passwordText, secureKey); + String decryptedMsg = encryptionAesUtil.decryptMsg(encryptedMsg, secureKey); + logger.info("\nEncrypted message:" + encryptedMsg.toString() + "\nText to be encrypted:" + passwordText + "\nDecrypted text:" + decryptedMsg); + Assert.assertNotEquals("Encrpted message should be not be equal to original message", passwordText, new String(encryptedMsg)); + Assert.assertEquals("Text recovered from encrypted message is as the expected: ", passwordText, decryptedMsg); + } + + @Test(expected = GeneralSecurityException.class) + public void testEncryptionExceptionThrownWhenKeyNotSet() throws Exception { + EncryptionAesUtil encryptionAesUtil = new EncryptionAesUtil(); + String passwordText = "somePassword6c3708b3"; + encryptionAesUtil.encryptMsg(passwordText, null); + } + + @Test + public void testEncryptionToAKeyFile() throws IOException, GeneralSecurityException { + EncryptionAesUtil encryptionAesUtil = new EncryptionAesUtil(); + + File temp = File.createTempFile("temp-file-name", ".tmp"); + String filePath = temp.getAbsolutePath(); + //delete so that file not actually created + Files.delete(Paths.get(filePath)); + + encryptionAesUtil.createKeyFileIfNotExisting(filePath); + + String passwordText = "somePassword6c3708b3"; + encryptionAesUtil.setCipherKeyFromFilePath(filePath); + + String encryptMsg = encryptionAesUtil.encryptMsg(passwordText); + String decryptedMsg = encryptionAesUtil.decryptMsg(encryptMsg); + logger.info("\nEncrypted key:" + encryptMsg + "\nText to be encrypted:" + passwordText + "\nDecrypted text:" + decryptedMsg); + + Files.delete(Paths.get(filePath)); + Assert.assertEquals("Recovered text are the same", passwordText, decryptedMsg); + } + + @Test + public void testGetFilePath() throws IOException, GeneralSecurityException { + EncryptionAesUtil encryptionAesUtil = new EncryptionAesUtil(); + String filePath = encryptionAesUtil.createUserProfileKeyName(); + Assert.assertTrue(filePath.contains(EncryptionAesUtil.DEFAULT_KEYFILE_NAME)); + filePath = encryptionAesUtil.createKeyFileIfNotExisting(filePath + ".store"); + Assert.assertTrue(Files.exists(Paths.get(filePath))); + Files.delete(Paths.get(filePath)); + } + + @Test + public void testAutoSetKeyFile() throws IOException, GeneralSecurityException { + boolean cleanup = false; + String filePath = null; + String filePathBak = null; + try { + EncryptionAesUtil encryptionAesUtil = new EncryptionAesUtil(); + String passwordText = "somePassword6c3708b3"; + filePath = encryptionAesUtil.createUserProfileKeyName(); + filePathBak = filePath + ".bak"; + if (Files.exists(Paths.get(filePath))) { + cleanup = true; + Files.move(Paths.get(filePath), Paths.get(filePathBak)); + } + // without setting the default key + String encryptedMsg = encryptionAesUtil.encryptMsg(passwordText); + String decryptedMsg = encryptionAesUtil.decryptMsg(encryptedMsg); + + Assert.assertTrue(Files.exists(Paths.get(filePath))); + Files.delete(Paths.get(filePath)); + + logger.info("\nEncrypted message:" + encryptedMsg.toString() + "\nText to be encrypted:" + passwordText + "\nDecrypted text:" + decryptedMsg); + Assert.assertNotEquals("Encrypted message should be not be equal to original message", passwordText, encryptedMsg); + Assert.assertEquals("Text recovered from encrypted message is as the expected: ", passwordText, decryptedMsg); + } finally { + if (cleanup) { + Files.move(Paths.get(filePathBak), Paths.get(filePath)); + } + } + } + + @Test /* test for single line encryption */ + public void testEncryptedDataSource() throws Exception { + String passwordText = "somePassword6c3708b3"; + EncryptionAesUtil encryptionAesUtil = new EncryptionAesUtil(); + String encryptedPassword = encryptionAesUtil.encryptMsg(passwordText); + EncryptedDataSource dataSource = new EncryptedDataSource(); + dataSource.setPassword(encryptedPassword); + @SuppressWarnings("deprecation") + String savedPassword = dataSource.getPassword(); + dataSource.close(); + Assert.assertNotEquals("Encrypted password should be not be equal to original password", passwordText, encryptedPassword); + Assert.assertEquals("Password recovered from EncryptedDataSource instance is as the expected: ", passwordText, savedPassword); + } +} diff --git a/src/test/java/com/salesforce/dataloader/PartnerConnectionForTest.java b/src/test/java/com/salesforce/dataloader/PartnerConnectionForTest.java new file mode 100644 index 000000000..e8af4d6b7 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/PartnerConnectionForTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashSet; +import java.util.List; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Assert; + +import com.sforce.soap.partner.DeleteResult; +import com.sforce.soap.partner.GetUserInfoResult; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.soap.partner.QueryResult; +import com.sforce.soap.partner.SaveResult; +import com.sforce.soap.partner.UpsertResult; +import com.sforce.soap.partner.fault.ApiFault; +import com.sforce.soap.partner.sobject.SObject; +import com.sforce.ws.ConnectionException; + +/** + * This class represents the base class for all data loader JUnit tests. TODO: ProcessScheduler test? TODO: Encryption + * test + * + * @author Lexi Viripaeff + * @author Alex Warshavsky + * @since 8.0 + */ +public class PartnerConnectionForTest extends ConfigTestBase { + private static Logger logger = DLLogManager.getLogger(PartnerConnectionForTest.class); + private PartnerConnection binding; + private static HashSet sObjectTypesCreatedOrUpserted = new HashSet(); + private static final Calendar testStartTime = Calendar.getInstance(); + private static boolean cleanedOnInitialize = false; + + public PartnerConnectionForTest(PartnerConnection binding) { + this.binding = binding; + if (!cleanedOnInitialize) { + deleteSfdcRecords("Account", ACCOUNT_WHERE_CLAUSE, 0); + deleteSfdcRecords("Contact", CONTACT_WHERE_CLAUSE, 0); + deleteSfdcRecords("TestField__c", TESTFIELD_WHERE_CLAUSE, 0); + sObjectTypesCreatedOrUpserted.add("Account"); + sObjectTypesCreatedOrUpserted.add("Contact"); + sObjectTypesCreatedOrUpserted.add("Task"); + sObjectTypesCreatedOrUpserted.add("TestField__c"); + cleanedOnInitialize = true; + } + } + + public SaveResult[] create(SObject[] sobjectArray) throws ConnectionException { + if (sobjectArray == null) { + return null; + } + synchronized(sObjectTypesCreatedOrUpserted) { + for (SObject sobject : sobjectArray) { + sObjectTypesCreatedOrUpserted.add(sobject.getType()); + } + } + return this.binding.create(sobjectArray); + } + + public UpsertResult[] upsert(String externalIdFieldName, SObject[] sobjectArray) throws ConnectionException { + if (sobjectArray == null) { + return null; + } + for (SObject sobject : sobjectArray) { + sObjectTypesCreatedOrUpserted.add(sobject.getType()); + } + return this.binding.upsert(externalIdFieldName, sobjectArray); + } + public DeleteResult[] delete(String[] idArray) throws ConnectionException { + return this.binding.delete(idArray); + } + + public GetUserInfoResult getUserInfo() throws ConnectionException { + return this.binding.getUserInfo(); + } + + public QueryResult query(String queryStr) throws ConnectionException { + return this.binding.query(queryStr); + } + + public QueryResult queryMore(String queryLocatorStr) throws ConnectionException { + return this.binding.queryMore(queryLocatorStr); + } + + public SObject[] retrieve(String fieldList, String sObjectType, String[] idArray) throws ConnectionException { + return this.binding.retrieve(fieldList, sObjectType, idArray); + } + + public void cleanup() { + if (this.binding == null) { + return; + } + synchronized(sObjectTypesCreatedOrUpserted) { + for (String type : sObjectTypesCreatedOrUpserted) { + deleteSfdcRecordsCreatedSinceTestStart(type); + } + } + } + + public void deleteSfdcRecordsCreatedSinceTestStart(String entityName) { + if (this.binding == null) { + return; + } + deleteSfdcRecordsCreatedSince(entityName, testStartTime); + } + + private void deleteSfdcRecordsCreatedSince(String entityName, Calendar calendar) { + if (this.binding == null) { + return; + } + String createdByClause = ""; + if (calendar != null) { + SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + String testStartTimeFormattedString = formatter.format(calendar.getTime()); + createdByClause = "CreatedDate > " + testStartTimeFormattedString; + } + deleteSfdcRecords(entityName, createdByClause, 0); + } + /** + * @param entityName + * @param whereClause + * @param retries + */ + public void deleteSfdcRecords(String entityName, String whereClause, + int retries) { + if (this.binding == null || retries >= 3) { + return; + } + String entitySpecificClause = ""; + switch (entityName.toLowerCase()) { + case "account": + entitySpecificClause = ACCOUNT_WHERE_CLAUSE; + break; + case "contact": + entitySpecificClause = CONTACT_WHERE_CLAUSE; + break; + case "testfield__c": + entitySpecificClause = TESTFIELD_WHERE_CLAUSE; + break; + } + if (whereClause == null || !whereClause.contains(entitySpecificClause)) { + if (whereClause == null || whereClause.isBlank()) { + whereClause = entitySpecificClause; + } else { + whereClause = "(" + whereClause + ") OR " + entitySpecificClause; + } + } + try { + // query for records + String soql = "select Id from " + entityName + " where " + whereClause; + logger.debug("Querying " + entityName + "s to delete with soql: " + soql); + int deletedCount = 0; + // now delete them 200 at a time.... we should use bulk api here + for (QueryResult qr = this.binding.query(soql); qr != null && qr.getRecords().length > 0; qr = qr.isDone() ? null + : this.binding.queryMore(qr.getQueryLocator())) { + deleteSfdcRecordsFromQueryResults(qr, 0); + deletedCount += qr.getRecords().length; + logger.debug("Deleted " + deletedCount + " out of " + qr.getSize() + " total deleted records"); + } + logger.info("Deleted " + deletedCount + " total objects of type " + entityName); + } catch (ApiFault e) { + deleteSfdcRecords(entityName, whereClause, retries); + Assert.fail("Failed to query " + entityName + "s to delete (" + + whereClause + "), error: " + e.getExceptionMessage()); + } catch (ConnectionException e) { + Assert.fail("Failed to query " + entityName + "s to delete (" + + whereClause + "), error: " + e.getMessage()); + } + } + + protected static final int SAVE_RECORD_LIMIT = 200; + + /** + * @param qryResult + */ + private void deleteSfdcRecordsFromQueryResults(QueryResult qryResult, int retries) { + if (this.binding == null) { + return; + } + List toDeleteIds = new ArrayList(); + for (int i = 0; i < qryResult.getRecords().length; i++) { + SObject record = qryResult.getRecords()[i]; + toDeleteIds.add(record.getId()); + } + deleteSfdcRecords(toDeleteIds.toArray(new String[] {}), 0); + } + + private void deleteSfdcRecords(String[] fullArrayOfIdsToDelete, int retries) { + if (this.binding == null) { + return; + } + try { + List toDeleteIdList = new ArrayList(); + for (int i = 0; i < fullArrayOfIdsToDelete.length; i++) { + toDeleteIdList.add(fullArrayOfIdsToDelete[i]); + // when SAVE_RECORD_LIMIT records are reached or + // if we're on the last query result record, do the delete + if (i > 0 && (i + 1) % SAVE_RECORD_LIMIT == 0 + || i == fullArrayOfIdsToDelete.length - 1) { + DeleteResult[] delResults = this.binding.delete( + toDeleteIdList.toArray(new String[] {})); + for (int j = 0; j < delResults.length; j++) { + DeleteResult delResult = delResults[j]; + if (!delResult.getSuccess()) { + logger.warn("Delete returned an error: " + delResult.getErrors()[0].getMessage(), + new RuntimeException()); + } + } + toDeleteIdList.clear(); + } + } + } catch (ApiFault e) { + if (checkBinding(++retries, e) != null) { + deleteSfdcRecords(fullArrayOfIdsToDelete, retries); + } + Assert.fail("Failed to delete records, error: " + e.getExceptionMessage()); + } catch (ConnectionException e) { + Assert.fail("Failed to delete records, error: " + e.getMessage()); + } + } +} diff --git a/src/test/java/com/salesforce/dataloader/TestBase.java b/src/test/java/com/salesforce/dataloader/TestBase.java index 23f9db940..4a3fbfee8 100644 --- a/src/test/java/com/salesforce/dataloader/TestBase.java +++ b/src/test/java/com/salesforce/dataloader/TestBase.java @@ -25,38 +25,43 @@ */ package com.salesforce.dataloader; +import com.salesforce.dataloader.client.BulkV1Client; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.Messages; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.exception.PasswordExpiredException; +import com.salesforce.dataloader.util.AppUtil; +import com.sforce.soap.partner.Connector; +import com.sforce.soap.partner.LoginResult; +import com.sforce.soap.partner.PartnerConnection; +import com.sforce.soap.partner.fault.ApiFault; +import com.sforce.ws.ConnectionException; +import com.sforce.ws.ConnectorConfig; + +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.rules.TestName; +import org.springframework.util.StringUtils; + import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; -import java.net.URI; -import java.net.URISyntaxException; import java.net.URL; +import java.util.HashMap; +import java.util.Map; import java.util.Properties; import java.util.regex.Pattern; -import org.apache.log4j.Logger; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.TestName; -import org.springframework.util.StringUtils; - -import com.salesforce.dataloader.client.ClientBase; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.Messages; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.exception.ControllerInitializationException; -import com.salesforce.dataloader.exception.PasswordExpiredException; -import com.sforce.soap.partner.Connector; -import com.sforce.soap.partner.LoginResult; -import com.sforce.soap.partner.PartnerConnection; -import com.sforce.soap.partner.fault.ApiFault; -import com.sforce.ws.ConnectionException; -import com.sforce.ws.ConnectorConfig; +import javax.xml.parsers.FactoryConfigurationError; /** * This class represents the base class for all data loader JUnit tests. TODO: ProcessScheduler test? TODO: Encryption @@ -66,15 +71,45 @@ * @author Alex Warshavsky * @since 8.0 */ -public abstract class TestBase { +abstract class TestBase { private static final Pattern INSIDE_BRACKETS_TEST_PARAMETERS = Pattern.compile("\\[.+\\]"); @Rule public TestName testName = new TestName(); + + /* ********* + * Start of the section declaring + * static variables that need to be initialized after loading test properties + * ********* + */ private static final Properties TEST_PROPS; + private static final String TEST_FILES_DIR; + protected static final String TEST_CONF_DIR; + private static final String TEST_DATA_DIR; + private static final String TEST_STATUS_DIR; + protected static final String DEFAULT_ACCOUNT_EXT_ID_FIELD; + + // logger + private static Logger logger; + /* ********* + * End of the section declaring + * static variables that need to be initialized after loading test properties + * ********* + */ + static { + // initialize the static variables that are dependent on test properties. TEST_PROPS = loadTestProperties(); + TEST_FILES_DIR = getProperty("testfiles.dir"); + TEST_CONF_DIR = TEST_FILES_DIR + File.separator + "conf"; + TEST_PROPS.put(AppConfig.CLI_OPTION_CONFIG_DIR_PROP, TEST_CONF_DIR); + TEST_DATA_DIR = TEST_FILES_DIR + File.separator + "data"; + TEST_STATUS_DIR = TEST_FILES_DIR + File.separator + "status"; + DEFAULT_ACCOUNT_EXT_ID_FIELD = getProperty("test.account.extid"); + + Map argsMap = new HashMap(); + argsMap.put(AppConfig.CLI_OPTION_CONFIG_DIR_PROP, getTestConfDir()); } private static Properties loadTestProperties() { @@ -98,40 +133,41 @@ protected static String getProperty(String testProperty) { return TEST_PROPS.getProperty(testProperty); } - private static final String API_CLIENT_NAME = "DataLoaderBatch/" + Controller.APP_VERSION; - - private static final String TEST_FILES_DIR = getProperty("testfiles.dir"); - private static final String TEST_CONF_DIR = TEST_FILES_DIR + File.separator + "conf"; - private static final String TEST_DATA_DIR = TEST_FILES_DIR + File.separator + "data"; - private static final String TEST_STATUS_DIR = TEST_FILES_DIR + File.separator + "status"; + private static final String API_CLIENT_NAME = "DataLoaderTestBatch/" + Controller.APP_VERSION; - protected static final String DEFAULT_ACCOUNT_EXT_ID_FIELD = getProperty("test.account.extid"); protected static final String DEFAULT_CONTACT_EXT_ID_FIELD = "NumberId__c"; protected static final String ACCOUNT_NUMBER_PREFIX = "ACCT"; protected static final String ACCOUNT_WHERE_CLAUSE = "AccountNumber__c like '" + ACCOUNT_NUMBER_PREFIX + "%'"; protected static final String CONTACT_TITLE_PREFIX = "CONTTL"; protected static final String CONTACT_WHERE_CLAUSE = "Title like '" + CONTACT_TITLE_PREFIX + "%'"; + protected static final String TESTFIELD_FIELD_PREFIX = "testfield__"; + protected static final String TESTFIELD_WHERE_CLAUSE = "TestField__c like '" + TESTFIELD_FIELD_PREFIX + "%'"; protected static final int SAVE_RECORD_LIMIT = 200; - // logger - private static Logger logger = Logger.getLogger(TestBase.class); - protected String baseName; // / base name of the test (without the "test") private Controller controller; String oldThreadName; - PartnerConnection binding; - + PartnerConnectionForTest binding; + @Before public void basicSetUp() throws Exception { File testStatusDir = new File(TEST_STATUS_DIR); if (!testStatusDir.exists()) testStatusDir.mkdirs(); // reset binding + if (this.binding != null) { + this.binding.cleanup(); + } this.binding = null; - setupTestName(); - setupController(); + } + + @After + public void cleanup() { + if (this.controller != null && this.binding != null) { + this.binding.cleanup(); + } } private void setupTestName() { @@ -147,17 +183,17 @@ private void setupTestName() { Thread.currentThread().setName(testName.getMethodName()); } - protected void setupController() { + protected void setupController(Map configOverrideMap) { // configure the Controller to point to our testing config - if (!System.getProperties().contains(Controller.CONFIG_DIR_PROP)) - System.setProperty(Controller.CONFIG_DIR_PROP, getTestConfDir()); + configOverrideMap.put(AppConfig.PROP_READ_ONLY_CONFIG_PROPERTIES, Boolean.TRUE.toString()); + if (!System.getProperties().contains(AppConfig.CLI_OPTION_CONFIG_DIR_PROP)) + System.setProperty(AppConfig.CLI_OPTION_CONFIG_DIR_PROP, getTestConfDir()); - if (controller == null) { - try { - controller = Controller.getInstance(testName.getMethodName(), true); - } catch (ControllerInitializationException e) { - fail("While initializing controller instance", e); - } + try { + controller = Controller.getInstance(configOverrideMap); + logger = DLLogManager.getLogger(TestBase.class); + } catch (Exception e) { + fail("While initializing controller instance", e); } } @@ -177,38 +213,61 @@ protected Controller getController() { return controller; } + String apiVersionForTheSession = null; /** * @return PartnerConnection - binding to use to call the salesforce API */ - protected PartnerConnection getBinding() { + protected PartnerConnectionForTest getBinding() { if(binding != null) { return binding; } - ConnectorConfig bindingConfig = getWSCConfig(); - logger.info("Getting binding for URL: " + bindingConfig.getAuthEndpoint()); - binding = newConnection(bindingConfig, 0); + ConnectorConfig bindingConfig; + + if (this.apiVersionForTheSession == null) { + String apiVersionToTry = PartnerClient.getCurrentAPIVersionInWSC(); + bindingConfig = getWSCConfig(apiVersionToTry); + logger.info("Getting binding for URL: " + bindingConfig.getAuthEndpoint()); + binding = newConnection(bindingConfig, 0, 0); + if (binding == null) { + logger.error("Failed to invoke server APIs of version " + apiVersionToTry); + apiVersionToTry = PartnerClient.getPreviousAPIVersionInWSC(); + bindingConfig = getWSCConfig(apiVersionToTry); + logger.info("Getting binding for URL: " + bindingConfig.getAuthEndpoint()); + binding = newConnection(bindingConfig, 0, 0); + if (binding == null) { + fail("Error logging in and getting a service binding for API version " + apiVersionToTry, new Exception()); + } + this.apiVersionForTheSession = apiVersionToTry; + } + } else { + bindingConfig = getWSCConfig(this.apiVersionForTheSession); + logger.info("Getting binding for URL: " + bindingConfig.getAuthEndpoint()); + binding = newConnection(bindingConfig, 0, 3); + if (binding == null) { + fail("Error logging in and getting a service binding for API version " + this.apiVersionForTheSession, new Exception()); + } + } return binding; } - protected ConnectorConfig getWSCConfig() { + protected ConnectorConfig getWSCConfig(String apiVersionStr) { ConnectorConfig bindingConfig = new ConnectorConfig(); - bindingConfig.setUsername(getController().getConfig().getString(Config.USERNAME)); - bindingConfig.setPassword(getController().getConfig().getString(Config.PASSWORD)); - String configEndpoint = getController().getConfig().getString(Config.ENDPOINT); + bindingConfig.setUsername(getController().getAppConfig().getString(AppConfig.PROP_USERNAME)); + bindingConfig.setPassword(getController().getAppConfig().getString(AppConfig.PROP_PASSWORD)); + String configEndpoint = getController().getAppConfig().getAuthEndpointForCurrentEnv(); if (!configEndpoint.equals("")) { //$NON-NLS-1$ - String serverPath; try { - serverPath = new URI(Connector.END_POINT).getPath(); - bindingConfig.setAuthEndpoint(configEndpoint + serverPath); - bindingConfig.setServiceEndpoint(configEndpoint + serverPath); - bindingConfig.setRestEndpoint(configEndpoint + ClientBase.REST_ENDPOINT); + PartnerClient.setAPIVersionForTheSession(apiVersionStr); + bindingConfig.setAuthEndpoint(configEndpoint + PartnerClient.getServicePath()); + bindingConfig.setServiceEndpoint(configEndpoint + PartnerClient.getServicePath()); // Partner SOAP service + bindingConfig.setRestEndpoint(configEndpoint + BulkV1Client.getServicePath()); // REST service: Bulk v1 bindingConfig.setManualLogin(true); // set long timeout for tests with larger data sets bindingConfig.setReadTimeout(5 * 60 * 1000); - if (getController().getConfig().getBoolean(Config.DEBUG_MESSAGES)) { + if (getController().getAppConfig().getBoolean(AppConfig.PROP_DEBUG_MESSAGES)) { bindingConfig.setTraceMessage(true); bindingConfig.setPrettyPrintXml(true); - String filename = getController().getConfig().getString(Config.DEBUG_MESSAGES_FILE); + String filename = getController().getAppConfig().getString(AppConfig.PROP_DEBUG_MESSAGES_FILE); if (!filename.isEmpty()) { try { bindingConfig.setTraceFile(filename); @@ -217,7 +276,7 @@ protected ConnectorConfig getWSCConfig() { } } } - } catch (URISyntaxException e) { + } catch (Exception e) { Assert.fail("Error parsing endpoint URL: " + Connector.END_POINT + ", error: " + e.getMessage()); } } @@ -229,7 +288,7 @@ protected ConnectorConfig getWSCConfig() { * @return PartnerConnection * @throws com.sforce.ws.ConnectionException */ - private PartnerConnection newConnection(ConnectorConfig bindingConfig, int retries) { + private PartnerConnectionForTest newConnection(ConnectorConfig bindingConfig, int retries, int maxRetries) { try { PartnerConnection newBinding = Connector.newConnection(bindingConfig); @@ -246,14 +305,13 @@ private PartnerConnection newConnection(ConnectorConfig bindingConfig, int retri newBinding.setSessionHeader(loginResult.getSessionId()); bindingConfig.setServiceEndpoint(loginResult.getServerUrl()); } - return newBinding; + return new PartnerConnectionForTest(newBinding); } catch (ConnectionException e) { // in case of exception try to get a connection again - if (retries < 3) { + if (retries < maxRetries) { retries++; - return newConnection(bindingConfig, retries); + return newConnection(bindingConfig, retries, maxRetries); } - fail("Error getting web service proxy binding", e); } // make eclipse happy return null; @@ -267,7 +325,7 @@ protected String getResourcePath(String path) { return getTestFile(path).getAbsolutePath(); } - protected String getTestConfDir() { + protected static String getTestConfDir() { return TEST_CONF_DIR; } @@ -286,7 +344,7 @@ protected String getTestStatusDir() { /** * @param e */ - protected PartnerConnection checkBinding(int retries, ApiFault e) { + protected PartnerConnectionForTest checkBinding(int retries, ApiFault e) { logger.info("Retry#" + retries + " getting a binding after an error. Code: " + e.getExceptionCode().toString() + ", detail: " + e.getExceptionMessage()); if (retries < 3) // && (e.getExceptionCode() == ExceptionCode.INVALID_SESSION_ID || @@ -311,5 +369,4 @@ protected void fail(Throwable t) { t.printStackTrace(new PrintWriter(stackTrace)); fail("Unexpected exception of type " + t.getClass().getCanonicalName(), t); } - } diff --git a/src/test/java/com/salesforce/dataloader/TestProgressMontitor.java b/src/test/java/com/salesforce/dataloader/TestProgressMontitor.java deleted file mode 100644 index 100908f15..000000000 --- a/src/test/java/com/salesforce/dataloader/TestProgressMontitor.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader; - -import com.salesforce.dataloader.action.progress.ILoaderProgress; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -/** - * Progress monitor for use by tests. - * - * @author Colin Jarvis - * @since 20.0 - */ -public class TestProgressMontitor implements ILoaderProgress { - - private String taskName; - private int workDone; - private int totalWork; - private boolean success; - private String message; - private final List subTasksInOrder = new ArrayList(); - private int numberBatchesTotal; - - @Override - public void beginTask(String name, int totalWork) { - this.taskName = name; - this.totalWork = totalWork; - } - - @Override - public void doneError(String message) { - this.success = false; - this.message = message; - } - - @Override - public void doneSuccess(String message) { - this.success = true; - this.message = message; - } - - @Override - public void worked(int worked) { - this.workDone += worked; - } - - @Override - public void setSubTask(String name) { - this.subTasksInOrder.add(name); - } - - @Override - public boolean isCanceled() { - return false; - } - - @Override - public void setNumberBatchesTotal(int numberBatchesTotal) { - this.numberBatchesTotal = numberBatchesTotal; - } - - public int getNumberBatchesTotal() { - return numberBatchesTotal; - } - - // /////////// getters for test verification ////////////////// - - public String getTaskName() { - return this.taskName; - } - - public int getTotalWork() { - return this.totalWork; - } - - public boolean isSuccess() { - return this.success; - } - - public String getMessage() { - return this.message; - } - - public int getNumWorked() { - return this.workDone; - } - - public List getSubTasks() { - return Collections.unmodifiableList(this.subTasksInOrder); - } - -} diff --git a/src/test/java/com/salesforce/dataloader/TestSetting.java b/src/test/java/com/salesforce/dataloader/TestSetting.java index ef5ae219b..b10148517 100644 --- a/src/test/java/com/salesforce/dataloader/TestSetting.java +++ b/src/test/java/com/salesforce/dataloader/TestSetting.java @@ -25,7 +25,7 @@ */ package com.salesforce.dataloader; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; /** * A test setting is used to modify the behavior of dataloader during test execution. @@ -35,14 +35,22 @@ */ public enum TestSetting { - BULK_API_ENABLED(Config.BULK_API_ENABLED, Boolean.TRUE), - BULK_API_DISABLED(Config.BULK_API_ENABLED, Boolean.FALSE), - BULK_API_ZIP_CONTENT_ENABLED(Config.BULK_API_ZIP_CONTENT, Boolean.TRUE), - BULK_API_SERIAL_MODE_ENABLED(Config.BULK_API_SERIAL_MODE, Boolean.TRUE), - WRITE_UTF8_ENABLED(Config.WRITE_UTF8, Boolean.TRUE), - WRITE_UTF8_DISABLED(Config.WRITE_UTF8, Boolean.FALSE), - READ_UTF8_ENABLED(Config.READ_UTF8, Boolean.TRUE), - READ_UTF8_DISABLED(Config.READ_UTF8, Boolean.FALSE); + BULK_API_ENABLED(AppConfig.PROP_BULK_API_ENABLED, Boolean.TRUE), + BULK_API_DISABLED(AppConfig.PROP_BULK_API_ENABLED, Boolean.FALSE), + BULK_API_ZIP_CONTENT_ENABLED(AppConfig.PROP_BULK_API_ZIP_CONTENT, Boolean.TRUE), + BULK_API_SERIAL_MODE_ENABLED(AppConfig.PROP_BULK_API_SERIAL_MODE, Boolean.TRUE), + BULK_API_CACHE_DAO_UPLOAD_ENABLED(AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO, Boolean.TRUE), + BULK_V2_API_ENABLED(AppConfig.PROP_BULKV2_API_ENABLED, Boolean.TRUE), + BULK_V2_API_DISABLED(AppConfig.PROP_BULKV2_API_ENABLED, Boolean.FALSE), + COMPOSITE_REST_API_DISABLED(AppConfig.PROP_UPDATE_WITH_EXTERNALID, Boolean.FALSE), + COMPOSITE_REST_API_ENABLED(AppConfig.PROP_UPDATE_WITH_EXTERNALID, Boolean.TRUE), + WRITE_UTF8_ENABLED(AppConfig.PROP_WRITE_UTF8, Boolean.TRUE), + WRITE_UTF8_DISABLED(AppConfig.PROP_WRITE_UTF8, Boolean.FALSE), + READ_UTF8_ENABLED(AppConfig.PROP_READ_UTF8, Boolean.TRUE), + READ_UTF8_DISABLED(AppConfig.PROP_READ_UTF8, Boolean.FALSE), + COMPRESSION_ENABLED(AppConfig.PROP_NO_COMPRESSION, Boolean.FALSE), + COMPRESSION_DISABLED(AppConfig.PROP_NO_COMPRESSION, Boolean.TRUE) + ; private final String parameter; private final Object value; diff --git a/src/test/java/com/salesforce/dataloader/action/ExtractTest.java b/src/test/java/com/salesforce/dataloader/action/ExtractTest.java index 31af71f60..7eb372dda 100644 --- a/src/test/java/com/salesforce/dataloader/action/ExtractTest.java +++ b/src/test/java/com/salesforce/dataloader/action/ExtractTest.java @@ -25,8 +25,11 @@ */ package com.salesforce.dataloader.action; +import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.exception.ExtractException; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.junit.Assert; import org.junit.Test; @@ -43,9 +46,9 @@ * @author Alex Warshavsky * @since 8.0 */ -public class ExtractTest { +public class ExtractTest extends ConfigTestBase { - private static final Logger logger = Logger.getLogger(ExtractTest.class); + private static final Logger logger = DLLogManager.getLogger(ExtractTest.class); @Test public void testQueryParsePositive() throws Exception { diff --git a/src/test/java/com/salesforce/dataloader/client/HttpClientTransportTest.java b/src/test/java/com/salesforce/dataloader/client/HttpClientTransportTest.java index b494c1834..71e5e8f72 100644 --- a/src/test/java/com/salesforce/dataloader/client/HttpClientTransportTest.java +++ b/src/test/java/com/salesforce/dataloader/client/HttpClientTransportTest.java @@ -35,10 +35,12 @@ import org.junit.Ignore; import org.junit.Test; +import com.salesforce.dataloader.ConfigTestBase; + /** * @author xbian */ -public class HttpClientTransportTest { +public class HttpClientTransportTest extends ConfigTestBase { @Ignore @Test // This is a test based a SNI test website where it will check if Server Name Indication is included diff --git a/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java b/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java index 11785ffa9..d0107965a 100644 --- a/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java +++ b/src/test/java/com/salesforce/dataloader/client/PartnerClientTest.java @@ -25,8 +25,8 @@ */ package com.salesforce.dataloader.client; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.dyna.ObjectField; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.dyna.ParentIdLookupFieldFormatter; import com.salesforce.dataloader.dyna.SforceDynaBean; import com.salesforce.dataloader.process.ProcessTestBase; import com.sforce.soap.partner.DeleteResult; @@ -42,9 +42,11 @@ import org.apache.commons.beanutils.DynaBean; import org.apache.commons.beanutils.DynaProperty; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -61,44 +63,45 @@ * @author Alex Warshavsky * @since 8.0 */ +@SuppressWarnings("unused") public class PartnerClientTest extends ProcessTestBase { @Test public void testPartnerClientConnect() throws Exception { PartnerClient client = new PartnerClient(getController()); - assertFalse(getController().getConfig().getBoolean(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN)); + assertFalse(getController().getAppConfig().getBoolean(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN)); boolean connect = client.connect(); assertTrue(connect); - assertNotNull(client.getClient()); + assertNotNull(client.getConnection()); client.connect(client.getSession()); - assertTrue(client.getClient().getDisableFeedTrackingHeader().isDisableFeedTracking()); + assertTrue(client.getConnection().getDisableFeedTrackingHeader().isDisableFeedTracking()); } @Test public void testPartnerClientNoUserName() throws ConnectionException { - Config config = getController().getConfig(); - String origUserName = config.getString(Config.USERNAME); + AppConfig appConfig = getController().getAppConfig(); + String origUserName = appConfig.getString(AppConfig.PROP_USERNAME); try { - config.setValue(Config.USERNAME, ""); + appConfig.setValue(AppConfig.PROP_USERNAME, ""); PartnerClient client = new PartnerClient(getController()); boolean connect = client.connect(); assertFalse("Should not connect with empty username", connect); } catch (RuntimeException e) { //make sure we get the right error message that mentions the username - assertTrue(e.getMessage().contains(Config.USERNAME)); + assertTrue(e.getMessage().contains(AppConfig.PROP_USERNAME)); } finally { - config.setValue(Config.USERNAME, origUserName); + appConfig.setValue(AppConfig.PROP_USERNAME, origUserName); } } @Test public void testPartnerClientSfdcInternalSessionIdConnect() throws Exception { - Config config = getController().getConfig(); + AppConfig appConfig = getController().getAppConfig(); - final String origUsername = config.getString(Config.USERNAME); - final String origPassword = config.getString(Config.PASSWORD); - final String origEndpoint = config.getString(Config.ENDPOINT); + final String origUsername = appConfig.getString(AppConfig.PROP_USERNAME); + final String origPassword = appConfig.getString(AppConfig.PROP_PASSWORD); + final String origEndpoint = appConfig.getAuthEndpointForCurrentEnv(); //login normally just to get sessionId and endpoint PartnerClient setupOnlyClient = new PartnerClient(getController()); @@ -108,33 +111,33 @@ public void testPartnerClientSfdcInternalSessionIdConnect() throws Exception { setupOnlyClient.disconnect(); try { - config.setValue(Config.USERNAME, ""); - config.setValue(Config.PASSWORD, ""); + appConfig.setValue(AppConfig.PROP_USERNAME, ""); + appConfig.setValue(AppConfig.PROP_PASSWORD, ""); - config.setValue(Config.SFDC_INTERNAL, true); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); - config.setValue(Config.ENDPOINT, endpoint); - config.setValue(Config.SFDC_INTERNAL_SESSION_ID, sessionId); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL, true); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); + appConfig.setAuthEndpointForCurrentEnv(endpoint); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID, sessionId); PartnerClient client = new PartnerClient(getController()); assertTrue(client.connect()); } finally { - config.setValue(Config.USERNAME, origUsername); - config.setValue(Config.PASSWORD, origPassword); - config.setValue(Config.ENDPOINT, origEndpoint); - config.setValue(Config.SFDC_INTERNAL, false); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); - config.setValue(Config.SFDC_INTERNAL_SESSION_ID, ""); + appConfig.setValue(AppConfig.PROP_USERNAME, origUsername); + appConfig.setValue(AppConfig.PROP_PASSWORD, origPassword); + appConfig.setAuthEndpointForCurrentEnv(origEndpoint); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID, ""); } } @Test public void testPartnerClientSfdcInternalSessionIdWithoutSfdcInternalConnect() throws Exception { - Config config = getController().getConfig(); + AppConfig appConfig = getController().getAppConfig(); - final String origUsername = config.getString(Config.USERNAME); - final String origPassword = config.getString(Config.PASSWORD); - final String origEndpoint = config.getString(Config.ENDPOINT); + final String origUsername = appConfig.getString(AppConfig.PROP_USERNAME); + final String origPassword = appConfig.getString(AppConfig.PROP_PASSWORD); + final String origEndpoint = appConfig.getAuthEndpointForCurrentEnv(); //login normally just to get sessionId and endpoint PartnerClient setupOnlyClient = new PartnerClient(getController()); @@ -144,13 +147,13 @@ public void testPartnerClientSfdcInternalSessionIdWithoutSfdcInternalConnect() t setupOnlyClient.disconnect(); try { - config.setValue(Config.USERNAME, ""); - config.setValue(Config.PASSWORD, ""); + appConfig.setValue(AppConfig.PROP_USERNAME, ""); + appConfig.setValue(AppConfig.PROP_PASSWORD, ""); - config.setValue(Config.SFDC_INTERNAL, false); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); - config.setValue(Config.ENDPOINT, endpoint); - config.setValue(Config.SFDC_INTERNAL_SESSION_ID, sessionId); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); + appConfig.setAuthEndpointForCurrentEnv(endpoint); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID, sessionId); PartnerClient client = new PartnerClient(getController()); client.connect(); @@ -161,28 +164,28 @@ public void testPartnerClientSfdcInternalSessionIdWithoutSfdcInternalConnect() t "Empty salesforce.com username specified. Please make sure that parameter sfdc.username is set to correct username.", e.getMessage()); } finally { - config.setValue(Config.USERNAME, origUsername); - config.setValue(Config.PASSWORD, origPassword); - config.setValue(Config.ENDPOINT, origEndpoint); - config.setValue(Config.SFDC_INTERNAL, false); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); - config.setValue(Config.SFDC_INTERNAL_SESSION_ID, ""); + appConfig.setValue(AppConfig.PROP_USERNAME, origUsername); + appConfig.setValue(AppConfig.PROP_PASSWORD, origPassword); + appConfig.setAuthEndpointForCurrentEnv(origEndpoint); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID, ""); } } @Test public void testIsSessionValidAlwaysTrueForSessionIdLogin() throws Exception { - Config config = getController().getConfig(); + AppConfig appConfig = getController().getAppConfig(); try { - config.setValue(Config.SFDC_INTERNAL, true); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL, true); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, true); PartnerClient client = new PartnerClient(getController()); assertTrue(client.isSessionValid()); } finally { - config.setValue(Config.SFDC_INTERNAL, false); - config.setValue(Config.SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL, false); + appConfig.setValue(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN, false); } } @@ -200,25 +203,28 @@ public void testDisconnect() throws Exception { @Test public void testSetEntityDescribe() throws Exception{ PartnerClient client = new PartnerClient(getController()); - assertTrue(client.setEntityDescribes()); assertNotNull(client.getDescribeGlobalResults()); - assertEquals(client.getEntityTypes().getSobjects().length, client - .getDescribeGlobalResults().size()); } @Test public void testDescribeSObjects() throws Exception { PartnerClient client = new PartnerClient(getController()); - assertTrue(client.getEntityDescribeMap().isEmpty()); - client.setEntityDescribes(); int numDescribes = 0; for (String objectType : client.getDescribeGlobalResults().keySet()){ - DescribeSObjectResult describeResult = client.describeSObject(objectType); - numDescribes++; - assertNotNull(describeResult); - assertEquals(objectType, describeResult.getName()); - assertEquals(numDescribes, client.getEntityDescribeMap().size()); + try { + DescribeSObjectResult describeResult = client.describeSObject(objectType); + numDescribes++; + assertNotNull(describeResult); + assertEquals(objectType, describeResult.getName()); + } catch (Exception ex) { + if (ex.getMessage().contains("jsonNot")) { + System.out.println("PartnerClient.testDescribeSObjects: Unable to call describeSObject for " + objectType); + } else { + throw ex; + + } + } } } @@ -246,17 +252,18 @@ public void testGetSforceField() throws Exception { assertTrue("Account Name not found ", hasName); } + @SuppressWarnings("unchecked") @Test public void testInsertBasic() throws Exception { // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass("Account"); + BasicDynaClass dynaClass; Map sforceMapping = new HashMap(); sforceMapping.put("Name", "name" + System.currentTimeMillis()); sforceMapping.put("Description", "the description"); // Account number is set for easier test data cleanup sforceMapping.put("AccountNumber", ACCOUNT_NUMBER_PREFIX + System.currentTimeMillis()); - + dynaClass = setupDynaClass("Account", (Collection)(Collection)(sforceMapping.values())); // now convert to a dynabean array for the client DynaBean sforceObj = dynaClass.newInstance(); @@ -278,10 +285,20 @@ public void testInsertBasic() throws Exception { @Test public void testUpdateBasic() throws Exception { + doTestUpdateBasic(false); + } + + @Test + public void testUpdateBasicWithoutCompression() throws Exception { + doTestUpdateBasic(true); + } + + @SuppressWarnings("unchecked") + private void doTestUpdateBasic(boolean noCompression) throws Exception { String id = getRandomAccountId(); // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass("Account"); + BasicDynaClass dynaClass; Map sforceMapping = new HashMap(); sforceMapping.put("Id", id); @@ -289,6 +306,7 @@ public void testUpdateBasic() throws Exception { sforceMapping.put("Description", "the new description"); // Account number is set for easier test data cleanup sforceMapping.put("AccountNumber", ACCOUNT_NUMBER_PREFIX + System.currentTimeMillis()); + dynaClass = setupDynaClass("Account", (Collection)(Collection)(sforceMapping.values())); // now convert to a dynabean array for the client DynaBean sforceObj = dynaClass.newInstance(); @@ -298,6 +316,8 @@ public void testUpdateBasic() throws Exception { List beanList = new ArrayList(); beanList.add(sforceObj); + + getController().getAppConfig().setValue(AppConfig.PROP_NO_COMPRESSION, noCompression); // get the client and make the insert call PartnerClient client = new PartnerClient(getController()); @@ -312,17 +332,19 @@ public void testUpdateBasic() throws Exception { /** * Basic failing - forgetting the id */ + @SuppressWarnings("unchecked") @Test public void testUpdateFailBasic() throws Exception { // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass("Account"); + BasicDynaClass dynaClass; Map sforceMapping = new HashMap(); sforceMapping.put("Name", "newname" + System.currentTimeMillis()); sforceMapping.put("Description", "the new description"); // Account number is set for easier test data cleanup sforceMapping.put("AccountNumber", ACCOUNT_NUMBER_PREFIX + System.currentTimeMillis()); + dynaClass = setupDynaClass("Account", (Collection)(Collection)(sforceMapping.values())); // now convert to a dynabean array for the client DynaBean sforceObj = dynaClass.newInstance(); @@ -391,7 +413,7 @@ public void testUpsertFkFailBasic() throws Exception { } private void doUpsertAccount(boolean upsertFk) throws Exception { - String origExtIdField = getController().getConfig().getString(Config.EXTERNAL_ID_FIELD); + String origExtIdField = getController().getAppConfig().getString(AppConfig.PROP_IDLOOKUP_FIELD); try { // make sure the external id is set @@ -414,7 +436,7 @@ private void doUpsertAccount(boolean upsertFk) throws Exception { doUpsertAccount(false); parentExtIdValue = getRandomExtId("Account", ACCOUNT_WHERE_CLAUSE, extIdValue); } - sforceMapping.put(ObjectField.formatAsString("Parent", extIdField), parentExtIdValue); + sforceMapping.put(new ParentIdLookupFieldFormatter(null, "Parent", extIdField).toString(), parentExtIdValue); } doUpsert("Account", sforceMapping); @@ -424,7 +446,7 @@ private void doUpsertAccount(boolean upsertFk) throws Exception { } private void doUpsertContact(boolean upsertFk) throws Exception { - String origExtIdField = getController().getConfig().getString(Config.EXTERNAL_ID_FIELD); + String origExtIdField = getController().getAppConfig().getString(AppConfig.PROP_IDLOOKUP_FIELD); try { // make sure the external id is set @@ -441,7 +463,7 @@ private void doUpsertContact(boolean upsertFk) throws Exception { // Add upsert on FK -- reference to an account if (upsertFk) { // remember original ext id field - String oldExtIdField = getController().getConfig().getString(Config.EXTERNAL_ID_FIELD); + String oldExtIdField = getController().getAppConfig().getString(AppConfig.PROP_IDLOOKUP_FIELD); String acctExtIdField = setExtIdField(DEFAULT_ACCOUNT_EXT_ID_FIELD); Object accountExtIdValue = getRandomExtId("Account", ACCOUNT_WHERE_CLAUSE, null); @@ -451,7 +473,7 @@ private void doUpsertContact(boolean upsertFk) throws Exception { doUpsertAccount(false); accountExtIdValue = getRandomExtId("Account", ACCOUNT_WHERE_CLAUSE, accountExtIdValue); } - sforceMapping.put(ObjectField.formatAsString("Account", acctExtIdField), accountExtIdValue); + sforceMapping.put(new ParentIdLookupFieldFormatter(null, "Account", acctExtIdField).toString(), accountExtIdValue); // restore ext id field setExtIdField(oldExtIdField); @@ -464,36 +486,14 @@ private void doUpsertContact(boolean upsertFk) throws Exception { } } - private void doUpsert(String entity, Map sforceMapping) throws Exception { - // now convert to a dynabean array for the client - // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass(entity); - - DynaBean sforceObj = dynaClass.newInstance(); - - // This does an automatic conversion of types. - BeanUtils.copyProperties(sforceObj, sforceMapping); - - List beanList = new ArrayList(); - beanList.add(sforceObj); - - // get the client and make the insert call - PartnerClient client = new PartnerClient(getController()); - UpsertResult[] results = client.loadUpserts(beanList); - for (UpsertResult result : results) { - if (!result.getSuccess()) { - Assert.fail("Upsert returned an error: " + result.getErrors()[0].getMessage()); - } - } - } - /** * Basic failing - forgetting the external id or foreign key external id */ + @SuppressWarnings("unchecked") private void doUpsertFailBasic(boolean upsertFk) throws Exception { // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass("Account"); + BasicDynaClass dynaClass; Map sforceMapping = new HashMap(); sforceMapping.put("Name", "newname" + System.currentTimeMillis()); @@ -507,8 +507,9 @@ private void doUpsertFailBasic(boolean upsertFk) throws Exception { if (upsertFk) { sforceMapping.put(extIdField, extIdValue); // forget to set the foreign key external id value - sforceMapping.put(ObjectField.formatAsString("Parent", extIdField), "bogus"); + sforceMapping.put(new ParentIdLookupFieldFormatter(null, "Parent", extIdField).toString(), "bogus"); } + dynaClass = setupDynaClass("Account", (Collection)(Collection)(sforceMapping.values())); // now convert to a dynabean array for the client DynaBean sforceObj = dynaClass.newInstance(); @@ -528,12 +529,13 @@ private void doUpsertFailBasic(boolean upsertFk) throws Exception { } } + @SuppressWarnings("unchecked") @Test public void testDeleteBasic() throws Exception { String id = getRandomAccountId(); // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass("Account"); + BasicDynaClass dynaClass; Map sforceMapping = new HashMap(); sforceMapping.put("Id", id); @@ -541,6 +543,7 @@ public void testDeleteBasic() throws Exception { sforceMapping.put("Description", "the description"); // Account number is set for easier test data cleanup sforceMapping.put("AccountNumber", ACCOUNT_NUMBER_PREFIX + System.currentTimeMillis()); + dynaClass = setupDynaClass("Account", (Collection)(Collection)(sforceMapping.values())); // now convert to a dynabean array for the client DynaBean sforceObj = dynaClass.newInstance(); @@ -568,13 +571,14 @@ public void testDeleteBasic() throws Exception { public void testDeleteFailBasic() throws Exception { // setup our dynabeans - BasicDynaClass dynaClass = setupDynaClass("Account"); + BasicDynaClass dynaClass; Map sforceMapping = new HashMap(); sforceMapping.put("name", "name" + System.currentTimeMillis()); sforceMapping.put("description", "the description"); // Account number is set for easier test data cleanup sforceMapping.put("AccountNumber", ACCOUNT_NUMBER_PREFIX + System.currentTimeMillis()); + dynaClass = setupDynaClass("Account", (Collection)(Collection)(sforceMapping.values())); // now convert to a dynabean array for the client DynaBean sforceObj = dynaClass.newInstance(); @@ -635,68 +639,4 @@ private String getRandomAccountId() throws ConnectionException { return records[0].getId(); } - - /** - * Make sure to set external id field - */ - private String setExtIdField(String extIdField) { - getController().getConfig().setValue(Config.EXTERNAL_ID_FIELD, extIdField); - return extIdField; - } - - /** - * Get a random account external id for upsert testing - * - * @param entity - * TODO - * @param whereClause - * TODO - * @param prevValue - * Indicate that the value should be different from the specified - * value or null if uniqueness not required - * @return String Account external id value - */ - private Object getRandomExtId(String entity, String whereClause, Object prevValue) throws ConnectionException { - - // insert couple of accounts so there're at least two records to work with - upsertSfdcRecords(entity, 2); - - // get the client and make the query call - String extIdField = getController().getConfig().getString(Config.EXTERNAL_ID_FIELD); - PartnerClient client = new PartnerClient(getController()); - // only get the records that have external id set, avoid nulls - String soql = "select " + extIdField + " from " + entity + " where " + whereClause + " and " + extIdField - + " != null"; - if (prevValue != null) { - soql += " and " - + extIdField - + "!= " - + (prevValue.getClass().equals(String.class) ? ("'" + prevValue + "'") : String - .valueOf(prevValue)); - } - QueryResult result = client.query(soql); - SObject[] records = result.getRecords(); - assertNotNull("Operation should return non-null values", records); - assertTrue("Operation should return 1 or more records", records.length > 0); - assertNotNull("Records should have non-null field: " + extIdField + " values", records[0] - .getField(extIdField)); - - return records[0].getField(extIdField); - } - - private BasicDynaClass setupDynaClass(String entity) throws ConnectionException { - getController().getConfig().setValue(Config.ENTITY, entity); - PartnerClient client = getController().getPartnerClient(); - if (!client.isLoggedIn()) { - client.connect(); - } - - getController().setFieldTypes(); - getController().setReferenceDescribes(); - DynaProperty[] dynaProps = SforceDynaBean.createDynaProps(getController().getPartnerClient().getFieldTypes(), getController()); - BasicDynaClass dynaClass = SforceDynaBean.getDynaBeanInstance(dynaProps); - SforceDynaBean.registerConverters(getController().getConfig()); - return dynaClass; - } - } diff --git a/src/test/java/com/salesforce/dataloader/config/ConfigTest.java b/src/test/java/com/salesforce/dataloader/config/ConfigTest.java new file mode 100644 index 000000000..4b9b6ad4e --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/config/ConfigTest.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.util.Map; + +import javax.xml.parsers.FactoryConfigurationError; + +import com.salesforce.dataloader.util.DLLogManager; +import org.apache.logging.log4j.Logger; +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.exception.ConfigInitializationException; + +public class ConfigTest extends ConfigTestBase { + private static Logger logger = DLLogManager.getLogger(ConfigTest.class); + + public ConfigTest() { + super(); + } + + @Test + public void testProperty_SELECTED_AUTH_ENVIRONMENT() { + try { + AppConfig appConfig = AppConfig.getInstance(getTestConfig()); + appConfig.setServerEnvironment(AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + String selectedAuthEnv = appConfig.getString(AppConfig.PROP_SELECTED_SERVER_ENVIRONMENT); + assertEquals(selectedAuthEnv, AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + } catch (ConfigInitializationException + | FactoryConfigurationError + | IOException e) { + logger.error("Failed to get config instance: " + e.getMessage()); + fail("Failed to get config instance:", e); + } + } + + @Test + public void testProperty_OAUTH_CLIENTID() { + try { + AppConfig appConfig = AppConfig.getInstance(getTestConfig()); + appConfig.setServerEnvironment(AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + String configuredClientId = appConfig.getClientIDForCurrentEnv(); + if (appConfig.getBoolean(AppConfig.PROP_BULK_API_ENABLED) + || appConfig.getBoolean(AppConfig.PROP_BULKV2_API_ENABLED)) { + assertTrue(configuredClientId != null && configuredClientId.startsWith("DataLoaderBulkUI")); + } else { + assertTrue(configuredClientId != null && configuredClientId.startsWith("DataLoaderPartnerUI")); + } + } catch (ConfigInitializationException + | FactoryConfigurationError + | IOException e) { + logger.error("Failed to get config instance: " + e.getMessage()); + fail("Failed to get config instance:", e); + } + } + + @Test + public void testProperty_OAUTH_CLIENTSECRET() { + try { + AppConfig appConfig = AppConfig.getInstance(getTestConfig()); + appConfig.setServerEnvironment(AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + String configuredClientSecret = appConfig.getOAuthClientSecretForCurrentEnv(); + assertTrue(configuredClientSecret == null || configuredClientSecret.isBlank()); + } catch (ConfigInitializationException + | FactoryConfigurationError + | IOException e) { + logger.error("Failed to get config instance: " + e.getMessage()); + fail("Failed to get config instance:", e); + } + } + + @Test + public void testProperty_OAUTH_SERVER() { + try { + AppConfig appConfig = AppConfig.getInstance(getTestConfig()); + appConfig.setServerEnvironment(AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + String configuredOAuthServer = appConfig.getAuthEndpointForCurrentEnv(); + String expectedOAuthServer = getProperty("test.endpoint"); + if (expectedOAuthServer == null || expectedOAuthServer.isBlank()) { + logger.info("Expected prefix is " + expectedOAuthServer); + logger.info("Actual prefix is " + configuredOAuthServer); + } + assertTrue(configuredOAuthServer.startsWith(expectedOAuthServer)); + + appConfig.setServerEnvironment(AppConfig.SERVER_SB_ENVIRONMENT_VAL); + configuredOAuthServer = appConfig.getAuthEndpointForCurrentEnv(); + assertTrue(configuredOAuthServer.startsWith(AppConfig.DEFAULT_ENDPOINT_URL_SANDBOX)); + } catch (ConfigInitializationException + | FactoryConfigurationError + | IOException e) { + logger.error("Failed to get config instance: " + e.getMessage()); + fail("Failed to get config instance:", e); + } + } + + + @Test + public void testProperty_OAUTH_REDIRECTURI() { + try { + Map testConfigMap = getTestConfig(); + AppConfig appConfig = AppConfig.getInstance(testConfigMap); + appConfig.setServerEnvironment(AppConfig.SERVER_PROD_ENVIRONMENT_VAL); + String configuredOAuthRedirectURI = appConfig.getOAuthRedirectURIForCurrentEnv(); + String expectedOAuthRedirectURIPrefix = getProperty("test.endpoint"); + if (expectedOAuthRedirectURIPrefix == null || expectedOAuthRedirectURIPrefix.isBlank()) { + logger.info("Expected prefix is " + expectedOAuthRedirectURIPrefix); + logger.info("Actual prefix is " + configuredOAuthRedirectURI); + } + assertTrue(configuredOAuthRedirectURI.startsWith(expectedOAuthRedirectURIPrefix)); + assertTrue(configuredOAuthRedirectURI.endsWith(AppConfig.OAUTH_REDIRECT_URI_SUFFIX)); + } catch (ConfigInitializationException + | FactoryConfigurationError + | IOException e) { + logger.error("Failed to get config instance: " + e.getMessage()); + fail("Failed to get config instance:", e); + } + } + + @Test + public void testProperty_PROP_AUTH_ENDPOINT_LEGACY() { + // This is to verify that legacy way to set the Auth endpoint continues to work. + try { + Map testConfigMap = getTestConfig(); + testConfigMap.put(AppConfig.PROP_AUTH_ENDPOINT_LEGACY, "https://mylegacyendpoint"); + AppConfig appConfig = AppConfig.getInstance(testConfigMap); + + // clear prod endpoint property so that legacy endpoint property is used + appConfig.setValue(AppConfig.PROP_AUTH_ENDPOINT_PROD, ""); + String configuredAuthEndpoint = appConfig.getAuthEndpointForCurrentEnv(); + assertTrue(configuredAuthEndpoint.startsWith("https://mylegacyendpoint")); + } catch (ConfigInitializationException + | FactoryConfigurationError + | IOException e) { + logger.error("Failed to get config instance: " + e.getMessage()); + fail("Failed to get config instance:", e); + } + } +} diff --git a/src/test/java/com/salesforce/dataloader/dao/CsvTest.java b/src/test/java/com/salesforce/dataloader/dao/CsvTest.java index 8ee7054f0..d2a6fe3c3 100644 --- a/src/test/java/com/salesforce/dataloader/dao/CsvTest.java +++ b/src/test/java/com/salesforce/dataloader/dao/CsvTest.java @@ -26,54 +26,109 @@ package com.salesforce.dataloader.dao; import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.io.FileInputStream; +import java.io.InputStream; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; -import com.salesforce.dataloader.TestBase; +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.dao.csv.CSVFileWriter; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.RowInterface; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -public class CsvTest extends TestBase { +public class CsvTest extends ConfigTestBase { private static final String COLUMN_1_NAME = "column1"; private static final String COLUMN_2_NAME = "column2"; private static final String COLUMN_3_NAME = "column3"; private List writeHeader; - private Row row1; - private Row row2; + private TableRow row1; + private TableRow row2; @Before public void createTestData() { writeHeader = new ArrayList(3); - writeHeader.add("COL1"); - writeHeader.add("COL2"); - writeHeader.add("COL3"); - - row1 = new Row(); - row1.put("COL1", "row1col1"); - row1.put("COL2", "row1col2"); - row1.put("COL3", "row1col3"); - - row2 = new Row(); - row2.put("COL1", "row2col1"); - row2.put("COL2", "row2col2"); - row2.put("COL3", "row2col3"); + writeHeader.add("column1"); + writeHeader.add("column2"); + writeHeader.add("column3"); + + TableHeader header = new TableHeader(writeHeader); + row1 = new TableRow(header); + row1.put(writeHeader.get(0), "row1-1"); + row1.put(writeHeader.get(1), "row1-2"); + row1.put(writeHeader.get(2), "row1-3"); + + row2 = new TableRow(header); + row2.put(writeHeader.get(0), "row2-1"); + row2.put(writeHeader.get(1), "row2-2"); + row2.put(writeHeader.get(2), "row2-3"); } - @Test public void testCSVReadBasic() throws Exception { - File f = new File(getTestDataDir(), "csvtext.csv"); + testCSVReadBasic("csvtext.csv"); + } + + @Test + public void testCSVReadUTF8BOMBasic() throws Exception{ + testCSVReadBasic("csvtext_BOM_UTF8.csv"); + assertTrue("did not find BOM in " + getTestDataDir() + "/csvtext_BOM_UTF8.csv", + hasBOM(getTestDataDir() + "/csvtext_BOM_UTF8.csv")); + } + + @Test + public void testCSVReadUTF16BEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-16BE"); + testCSVReadBasic("csvtext_BOM_UTF16BE.csv"); + assertTrue("did not find BOM in " + getTestDataDir() + "/csvtext_BOM_UTF16BE.csv", + hasBOM(getTestDataDir() + "/csvtext_BOM_UTF16BE.csv")); + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, ""); + } + + @Test + public void testCSVReadUTF16LEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-16LE"); + testCSVReadBasic("csvtext_BOM_UTF16LE.csv"); + assertTrue("did not find BOM in " + getTestDataDir() + "/csvtext_BOM_UTF16LE.csv", + hasBOM(getTestDataDir() + "/csvtext_BOM_UTF16LE.csv")); + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, ""); + } + + @Test + public void testCSVReadUTF32LEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-32LE"); + testCSVReadBasic("csvtext_BOM_UTF32LE.csv"); + assertTrue("did not find BOM in " + getTestDataDir() + "/csvtext_BOM_UTF32LE.csv", + hasBOM(getTestDataDir() + "/csvtext_BOM_UTF32LE.csv")); + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, ""); + } + + @Test + public void testCSVReadUTF32BEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-32BE"); + testCSVReadBasic("csvtext_BOM_UTF32BE.csv"); + assertTrue("did not find BOM in " + getTestDataDir() + "/csvtext_BOM_UTF32BE.csv", + hasBOM(getTestDataDir() + "/csvtext_BOM_UTF32BE.csv")); + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, ""); + } + + private void testCSVReadBasic(String csvFile) throws Exception { + File f = new File(getTestDataDir(), csvFile); assertTrue(f.exists()); assertTrue(f.canRead()); - CSVFileReader csv = new CSVFileReader(f, getController().getConfig()); + CSVFileReader csv = new CSVFileReader(f, getController().getAppConfig(), false, false); csv.open(); List headerRow = csv.getColumnNames(); @@ -81,25 +136,102 @@ public void testCSVReadBasic() throws Exception { assertEquals(COLUMN_2_NAME, headerRow.get(1)); assertEquals(COLUMN_3_NAME, headerRow.get(2)); - Row firstRow = csv.readRow(); + TableRow firstRow = csv.readTableRow(); assertEquals("row1-1", firstRow.get(COLUMN_1_NAME)); assertEquals("row1-2", firstRow.get(COLUMN_2_NAME)); assertEquals("row1-3", firstRow.get(COLUMN_3_NAME)); - Row secondRow = csv.readRow(); + TableRow secondRow = csv.readTableRow(); assertEquals("row2-1", secondRow.get(COLUMN_1_NAME)); assertEquals("row2-2", secondRow.get(COLUMN_2_NAME)); assertEquals("row2-3", secondRow.get(COLUMN_3_NAME)); csv.close(); } + + + public static boolean hasBOM(String filePath) throws IOException { + try (InputStream is = new FileInputStream(filePath)) { + byte[] bom = new byte[3]; + if (is.read(bom) == 3) { + boolean bomFound = false; + // UTF-8 case + bomFound = bom[0] == (byte) 0xEF && bom[1] == (byte) 0xBB && bom[2] == (byte) 0xBF; + if (!bomFound) { + // UTF-16BE, UTF-32BE, UTF-32LE cases + bomFound = bom[0] == (byte)0xFE && bom[1] == (byte) 0xFF; + } + if (!bomFound) { + // UTF16-LE case + bomFound = bom[0] == (byte)0xFF && bom[1] == (byte) 0xFE; + } + return bomFound; + } + } + return false; + } @Test public void testCSVWriteBasic() throws Exception { - File f = new File(getTestDataDir(), "csvtestTemp.csv"); + doTestCSVWriteBasic(AppUtil.COMMA); + } + + @Test + public void testCSVWriteUTF8BOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-8"); + getController().getAppConfig().setValue(AppConfig.PROP_WRITE_CHARSET, "UTF-8"); + doTestCSVWriteBasic(AppUtil.COMMA); + } + + @Test + public void testCSVWriteUTF16LEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-16LE"); + getController().getAppConfig().setValue(AppConfig.PROP_WRITE_CHARSET, "UTF-16LE"); + doTestCSVWriteBasic(AppUtil.COMMA); + } + + @Test + public void testCSVWriteUTF16BEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-16BE"); + getController().getAppConfig().setValue(AppConfig.PROP_WRITE_CHARSET, "UTF-16BE"); + doTestCSVWriteBasic(AppUtil.COMMA); + } + + @Test + public void testCSVWriteUTF32LEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-32LE"); + getController().getAppConfig().setValue(AppConfig.PROP_WRITE_CHARSET, "UTF-32LE"); + doTestCSVWriteBasic(AppUtil.COMMA); + } + + @Test + public void testCSVWriteUTF32BEBOMBasic() throws Exception{ + getController().getAppConfig().setValue(AppConfig.PROP_READ_CHARSET, "UTF-32BE"); + getController().getAppConfig().setValue(AppConfig.PROP_WRITE_CHARSET, "UTF-32BE"); + doTestCSVWriteBasic(AppUtil.COMMA); + } + + @Test + public void testCSVWriteBasicWithDashDelimiter() throws Exception { + doTestCSVWriteBasic("-"); + } + + @Test + public void testCSVWriteBasicWithColonDelimiter() throws Exception { + doTestCSVWriteBasic(":"); + } + + @Test + public void testCSVWriteBasicWithTabDelimiter() throws Exception { + doTestCSVWriteBasic(AppUtil.TAB); + } + + private String writeCSVFilename = getTestDataDir() + "/csvtestTemp.csv"; + private void doTestCSVWriteBasic(String delimiter) throws Exception { + File f = new File(writeCSVFilename); String path = f.getAbsolutePath(); - CSVFileWriter writer = new CSVFileWriter(path, getController().getConfig()); - List rowList = new ArrayList(); + CSVFileWriter writer = new CSVFileWriter(path, getController().getAppConfig(), delimiter); + List rowList = new ArrayList(); rowList.add(row1); rowList.add(row2); @@ -109,35 +241,38 @@ public void testCSVWriteBasic() throws Exception { writer.writeRowList(rowList); writer.close(); + assertTrue("did not find BOM in " + path, hasBOM(path)); - compareWriterFile(path); - + compareWriterFile(path, delimiter, false, false); // 3rd param false and 4th param false => CSV for a upload + compareWriterFile(path, delimiter, false, true); // 3rd param false and 4th param true => query result CSV + compareWriterFile(path, delimiter, true, true); // 3rd param is set to true => upload result CSV + compareWriterFile(path, delimiter, true, false); // 3rd param is set to true => upload result CSV f.delete(); } - + @Test public void testReadingSeparatedValues () throws Exception { File f = new File(getTestDataDir(), "csvSeparator.csv"); assertTrue(f.exists()); assertTrue(f.canRead()); - getController().getConfig().setValue("loader.csvOther", true); - getController().getConfig().setValue("loader.csvOtherValue", "!"); + getController().getAppConfig().setValue("loader.csvOther", true); + getController().getAppConfig().setValue("loader.csvOtherValue", "!"); - CSVFileReader csv = new CSVFileReader(f, getController().getConfig()); + CSVFileReader csv = new CSVFileReader(f, getController().getAppConfig(), false, false); csv.open(); - Row firstRow = csv.readRow(); + TableRow firstRow = csv.readTableRow(); assertEquals("somev1", firstRow.get("some")); - assertEquals(4, firstRow.size()); - Row secondRow = csv.readRow(); + assertEquals(4, firstRow.getNonEmptyCellsCount()); + TableRow secondRow = csv.readTableRow(); assertEquals("somev2", secondRow.get("some")); csv.close(); - getController().getConfig().setValue("loader.csvOther", false); - csv = new CSVFileReader(f, getController().getConfig()); + getController().getAppConfig().setValue("loader.csvOther", false); + csv = new CSVFileReader(f, getController().getAppConfig(), false, false); csv.open(); - firstRow = csv.readRow(); + firstRow = csv.readTableRow(); assertEquals("col12!somev1", firstRow.get("column2!some")); - assertEquals(3, firstRow.size()); + assertEquals(3, firstRow.getNonEmptyCellsCount()); csv.close(); } @@ -147,13 +282,13 @@ public void testReadingEscapedValues() throws Exception { assertTrue(f.exists()); assertTrue(f.canRead()); - CSVFileReader csv = new CSVFileReader(f, getController().getConfig()); + CSVFileReader csv = new CSVFileReader(f, getController().getAppConfig(), false, false); csv.open(); - Row firstRow = csv.readRow(); + TableRow firstRow = csv.readTableRow(); assertEquals("\"The Best\" Account", firstRow.get(COLUMN_1_NAME)); - Row secondRow = csv.readRow(); + TableRow secondRow = csv.readTableRow(); assertEquals("The \"Best\" Account", secondRow.get(COLUMN_1_NAME)); csv.close(); @@ -161,11 +296,11 @@ public void testReadingEscapedValues() throws Exception { @Test public void testCsvWithManyRowsCanBeParsed() throws Exception { - CSVFileReader csvFileReader = new CSVFileReader(new File(getTestDataDir(), "20kRows.csv"), getController().getConfig()); + CSVFileReader csvFileReader = new CSVFileReader(new File(getTestDataDir(), "20kRows.csv"), getController().getAppConfig(), false, false); csvFileReader.open(); assertEquals(20000, csvFileReader.getTotalRows()); int count = 0; - for(Row row = csvFileReader.readRow(); row != null; row = csvFileReader.readRow(), count++); + for(TableRow row = csvFileReader.readTableRow(); row != null; row = csvFileReader.readTableRow(), count++); assertEquals(20000, count); } @@ -175,10 +310,41 @@ public void testCsvWithManyRowsCanBeParsed() throws Exception { * @param filePath */ - private void compareWriterFile(String filePath) throws Exception { - CSVFileReader csv = new CSVFileReader(filePath, getController()); - csv.open(); - + private void compareWriterFile(String filePath, String delimiterStr, boolean ignoreDelimiterConfig, boolean isQueryResultsCSV) throws Exception { + AppConfig appConfig = getController().getAppConfig(); + String storedDelimiter; + boolean storedCsvDelimiterComma = false, storedCsvDelimiterTab = false, storedCsvDelimiterOther = false; + if (isQueryResultsCSV) { + storedDelimiter = appConfig.getString(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS, delimiterStr); + } else { + storedDelimiter = appConfig.getString(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE); + storedCsvDelimiterComma = appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_COMMA); + storedCsvDelimiterTab = appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_TAB); + storedCsvDelimiterOther = appConfig.getBoolean(AppConfig.PROP_CSV_DELIMITER_OTHER); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_COMMA, false); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_TAB, false); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER, false); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE, delimiterStr); + if (AppUtil.COMMA.equals(delimiterStr)) { + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_COMMA, true); + } else if (" ".equals(delimiterStr)) { + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_TAB, true); + } else { + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER, true); + storedDelimiter = appConfig.getString(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE, delimiterStr); + } + } + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS, delimiterStr); + CSVFileReader csv = new CSVFileReader(new File(filePath), appConfig, ignoreDelimiterConfig, isQueryResultsCSV); + try { + csv.open(); + } catch (Exception e) { + assertTrue("Exception reading header row: " + e.getMessage(), ignoreDelimiterConfig && !delimiterStr.equals(AppUtil.COMMA)); + csv.close(); + return; + } //check that the header is the same as what we wanted to write List headerRow = csv.getColumnNames(); for (int i = 0; i < writeHeader.size(); i++) { @@ -186,16 +352,24 @@ private void compareWriterFile(String filePath) throws Exception { } //check that row 1 is valid - Row firstRow = csv.readRow(); + TableRow firstRow = csv.readTableRow(); for (String headerColumn : writeHeader) { assertEquals(row1.get(headerColumn), firstRow.get(headerColumn)); } //check that row 2 is valid - Row secondRow = csv.readRow(); + TableRow secondRow = csv.readTableRow(); for (String headerColumn : writeHeader) { assertEquals(row2.get(headerColumn), secondRow.get(headerColumn)); } csv.close(); + if (isQueryResultsCSV) { + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_FOR_QUERY_RESULTS, storedDelimiter); + } else { + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER_VALUE, storedDelimiter); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_COMMA, storedCsvDelimiterComma); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_TAB, storedCsvDelimiterTab); + appConfig.setValue(AppConfig.PROP_CSV_DELIMITER_OTHER, storedCsvDelimiterOther); + } } } diff --git a/src/test/java/com/salesforce/dataloader/dao/RichTextHTMLEncodingTest.java b/src/test/java/com/salesforce/dataloader/dao/RichTextHTMLEncodingTest.java new file mode 100644 index 000000000..59b9bdd90 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/dao/RichTextHTMLEncodingTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.dao; + +import static org.junit.Assert.assertEquals; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.text.StringEscapeUtils; +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.action.visitor.DAOLoadVisitor; +import com.salesforce.dataloader.config.AppConfig; + +public class RichTextHTMLEncodingTest extends ConfigTestBase { + String regex = AppConfig.DEFAULT_RICHTEXT_REGEX; + + @Test + public void testNoHTMLTags() throws Exception { + String origText = " a "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 4, getSpaceChars(convertedText.substring(0, 24))); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 2, getSpaceChars(convertedText.substring(25))); + + } + + @Test + public void testHTMLTagAttrsWithDoubleQuotes() throws Exception { + String tag = ""; + String origText = tag + " a "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 4, getSpaceChars(convertedText.substring( + tag.length(), + tag.length()+24))); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 2, getSpaceChars(convertedText.substring(tag.length()+30))); + } + + @Test + public void testHTMLTagWithoutClosingQuote() throws Exception { + String tag = ""; // skip closing doublequotes in style attribute + String origText = tag + " a

    "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect conversion of " + origText, "<", convertedText.substring(0, 4)); + } + + @Test + public void testHTMLTagAttrsWithSingleQuotes() throws Exception { + String tag = ""; + String origText = tag + " a "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 4, getSpaceChars(convertedText.substring( + tag.length(), + tag.length()+24))); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 2, getSpaceChars(convertedText.substring(tag.length()+34))); + } + + @Test + public void testLTAndGTCharsInString() throws Exception { + String origText = " <0 or >1 "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + true, convertedText.contains("<")); // text interpret as containing a HTML tag + + origText = "
        " + + "leading" + + "     space " + + "and" + + " ending" + + " tab" + + "

    " + + "
      " + + "
    1. line 1" + + "
        " + + "
      1. line 1a
      2. " + + "
      3. line 1b<>
      4. " + + "
      " + + "
    2. " + + "
    3. " + + "line 2 " + + "
    4. " + + "
    \n" + + ""; + String origText = tag + " a

    "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect conversion of " + origText, "<", convertedText.substring(0, 1)); + String[] parts = convertedText.split("1a"); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, 2, parts.length); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + 4, getSpaceChars(parts[1].substring(0,25))); + + // verify that the first < char and the 2nd > char in the line "line 1b" are HTML encoded + parts = convertedText.split("1b"); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, 2, parts.length); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + "<", parts[1].substring(0,4)); + + parts = parts[1].split(""); + parts = parts[0].split(""); + assertEquals("Incorrect encoding of whitespace characters in string" + origText, + ">", parts[1].substring(0,4)); + } + + @Test + public void testSingleHTMLTagNoWhitespaceInText() throws Exception { + String origText = "\"dlscreenshot\""; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect conversion of " + origText, origText.length(), convertedText.length()); + } + + @Test + public void testMultipleHTMLTagNoWhitespaceInText() throws Exception { + String origText = "



    "; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + assertEquals("Incorrect conversion of " + origText, origText.length(), convertedText.length()); + } + + @Test + public void testHTMLEncodedString() throws Exception { + String origText = " & & < * $ ~ % "6400L -37° 🌈 \n1 \r2 \r\n3"; + String convertedText = DAOLoadVisitor.convertToHTMLFormatting(origText, regex); + int diff = convertedText.length() - origText.length(); + assertEquals("Incorrect conversion of " + origText, origText.length() + 45, convertedText.length()); + + String unescapedOrigText = StringEscapeUtils.unescapeHtml4(origText); + String unescapedConvertedText = StringEscapeUtils.unescapeHtml4(convertedText); + diff = unescapedConvertedText.length() - unescapedOrigText.length(); + assertEquals("Incorrect conversion of " + origText, + unescapedOrigText.length()+11, + unescapedConvertedText.length()); + } + + + private static final String HTML_WHITESPACE_ENCODING = " "; + private static final Pattern HTML_WHITESPACE_PATTERN = Pattern.compile(HTML_WHITESPACE_ENCODING); + + private int getSpaceChars(String text) { + Matcher matcher = HTML_WHITESPACE_PATTERN.matcher(text); + int count = 0; + while (matcher.find()) { + count++; + } + return count; + } +} diff --git a/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTest.java b/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTest.java index 4df5b9062..fe550c919 100644 --- a/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTest.java +++ b/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTest.java @@ -25,18 +25,19 @@ */ package com.salesforce.dataloader.dao.database; -import com.salesforce.dataloader.TestBase; +import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.database.DatabaseTestUtil.DateType; import com.salesforce.dataloader.exception.DataAccessObjectException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import com.salesforce.dataloader.util.AccountRowComparator; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; -import java.sql.Time; import java.sql.Timestamp; import java.util.Arrays; import java.util.Collections; @@ -53,9 +54,9 @@ * @author Alex Warshavsky * @since 8.0 */ -public class DatabaseTest extends TestBase { +public class DatabaseTest extends ConfigTestBase { - private static final Logger logger = Logger.getLogger(DatabaseReader.class); + private static final Logger logger = DLLogManager.getLogger(DatabaseReader.class); private static final String[] VALIDATE_COLS = { DatabaseTestUtil.EXT_ID_COL, DatabaseTestUtil.SFDC_ID_COL, DatabaseTestUtil.NAME_COL, DatabaseTestUtil.PHONE_COL, DatabaseTestUtil.REVENUE_COL, @@ -119,7 +120,7 @@ public void testDatabaseDateMappingNull() throws Exception { } private void doTestDatabaseDateMapping(DatabaseTestUtil.DateType dateType, boolean verifyDates) throws Exception { - List> dateClass = Arrays.asList(java.sql.Date.class, Time.class, Timestamp.class); + List> dateClass = Arrays.asList(java.sql.Date.class, Timestamp.class); for (Class sqlType : dateClass) { try { // insert some data @@ -137,10 +138,10 @@ private static void verifyDbInsertOrUpdate(Controller theController, boolean isI logger.info("Verifying database success for '" + (isInsert ? "insert" : "update") + "' operation"); try { // sort order is reverse between insert and update - reader = new DatabaseReader(theController.getConfig(), "queryAccountAll"); + reader = new DatabaseReader(theController.getAppConfig(), "queryAccountAll"); reader.open(); int readBatchSize = 1000; - List readRowList = reader.readRowList(readBatchSize); + List readRowList = reader.readTableRowList(readBatchSize); int rowsProcessed = 0; assertNotNull("Error reading " + readBatchSize + " rows", readRowList); while(readRowList.size() > 0) { @@ -149,22 +150,18 @@ private static void verifyDbInsertOrUpdate(Controller theController, boolean isI Collections.sort(readRowList, new AccountRowComparator(!isInsert)); logger.info("Verifying database success for next " + (rowsProcessed + readRowList.size()) + " rows"); for (int i=0; i < readRowList.size(); i++) { - Row readRow = readRowList.get(i); + TableRow readRow = readRowList.get(i); assertNotNull("Error reading data row #" + i + ": the row shouldn't be null", readRow); - assertTrue("Error reading data row #" + i + ": the row shouldn't be empty", readRow.size() > 0); - Row expectedRow = DatabaseTestUtil.getInsertOrUpdateAccountRow(isInsert, rowsProcessed, DatabaseTestUtil.DateType.VALIDATION); + assertTrue("Error reading data row #" + i + ": the row shouldn't be empty", readRow.getNonEmptyCellsCount() > 0); + TableRow expectedRow = DatabaseTestUtil.getInsertOrUpdateAccountRow(isInsert, rowsProcessed, DatabaseTestUtil.DateType.VALIDATION); // verify all expected data - for(String colName : VALIDATE_COLS) { - if(validateDates && colName.equals(DateType.DATE)) { - verifyCol(DatabaseTestUtil.LAST_UPDATED_COL, readRow, expectedRow); - } else { - verifyCol(colName, readRow, expectedRow); - } + for (String colName : VALIDATE_COLS) { + verifyCol(colName, readRow, expectedRow); } rowsProcessed++; } - readRowList = reader.readRowList(readBatchSize); + readRowList = reader.readTableRowList(readBatchSize); assertNotNull("Error reading " + readBatchSize + " rows", readRowList); } } finally { @@ -172,7 +169,7 @@ private static void verifyDbInsertOrUpdate(Controller theController, boolean isI } } - private static void verifyCol(String colName, Row row, Row expectedRow) { + private static void verifyCol(String colName, TableRow row, TableRow expectedRow) { Object actualValue = row.get(colName); Object expectedValue = expectedRow.get(colName); assertNotNull("actual value is null", actualValue); diff --git a/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTestUtil.java b/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTestUtil.java index cb3f23fd8..780616c50 100644 --- a/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTestUtil.java +++ b/src/test/java/com/salesforce/dataloader/dao/database/DatabaseTestUtil.java @@ -25,13 +25,18 @@ */ package com.salesforce.dataloader.dao.database; +import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; + import junit.framework.TestCase; -import org.apache.commons.dbcp.BasicDataSource; -import org.apache.log4j.Logger; +import org.apache.commons.dbcp2.BasicDataSource; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.jdbc.support.rowset.SqlRowSet; @@ -53,9 +58,10 @@ * @author Alex Warshavsky * @since 8.0 */ -public class DatabaseTestUtil { +@SuppressWarnings("serial") +public class DatabaseTestUtil extends ConfigTestBase { - private static final Logger logger = Logger.getLogger(DatabaseTestUtil.class); + private static final Logger logger = DLLogManager.getLogger(DatabaseTestUtil.class); public enum DateType {CALENDAR, DATE, STRING, VALIDATION, NULL;}; @@ -75,7 +81,7 @@ public enum DateType {CALENDAR, DATE, STRING, VALIDATION, NULL;}; put(REVENUE_COL, "decimal"); put(LAST_UPDATED_COL, "date"); put(ACCOUNT_NUMBER_COL, "varchar(20)"); - put("system_modstamp", "date default sysdate not null"); + put("system_modstamp", "date default CURRENT_TIMESTAMP not null"); }}; public static void insertOrUpdateAccountsDb(Controller theController, boolean isInsert, int numAccounts, boolean insertNulls) { @@ -93,18 +99,18 @@ public static void insertOrUpdateAccountsDb(Controller theController, boolean is SqlConfig sqlConfig = dbConfig.getSqlConfig(); // override the configured sqltype for the last_update column sqlConfig.getSqlParams().put(LAST_UPDATED_COL, dateClass.getName()); - writer = new DatabaseWriter(theController.getConfig(), dbConfigName, dataSource, sqlConfig); + writer = new DatabaseWriter(theController.getAppConfig(), dbConfigName, dataSource, sqlConfig); writer.open(); - List accountRowList = new ArrayList(); + List accountRowList = new ArrayList(); int rowsProcessed = 0; for(int i=0; i < numAccounts; i++) { - Row accountRow = getInsertOrUpdateAccountRow(isInsert, i, dateType, insertNulls); + TableRow accountRow = getInsertOrUpdateAccountRow(isInsert, i, dateType, insertNulls); accountRowList.add(accountRow); if(accountRowList.size() >= 1000 || i == (numAccounts-1)) { rowsProcessed += accountRowList.size(); writer.writeRowList(accountRowList); logger.info("Written " + rowsProcessed + " of " + numAccounts + " total accounts using database config: " + dbConfigName); - accountRowList = new ArrayList(); + accountRowList = new ArrayList(); } } } catch (DataAccessObjectInitializationException e) { @@ -117,12 +123,12 @@ public static void insertOrUpdateAccountsDb(Controller theController, boolean is } } - public static Row getInsertOrUpdateAccountRow(boolean isInsert, int seqNum, DateType dateType) { + public static TableRow getInsertOrUpdateAccountRow(boolean isInsert, int seqNum, DateType dateType) { return getInsertOrUpdateAccountRow(isInsert, seqNum, dateType, false); } public static DatabaseConfig getDatabaseConfig(Controller controller, String dbConfigName) { - String dbConfigFilename = controller.getConfig().constructConfigFilePath( + String dbConfigFilename = controller.getAppConfig().constructConfigFilePath( DatabaseContext.DEFAULT_CONFIG_FILENAME); return DatabaseConfig.getInstance(dbConfigFilename, dbConfigName); } @@ -138,8 +144,18 @@ public static DatabaseConfig getDatabaseConfig(Controller controller, String dbC * @param dateType Type for the date field values * @return Row containing account data based on seqNum */ - public static Row getInsertOrUpdateAccountRow(boolean isInsert, int seqNum, DateType dateType, boolean insertNulls) { - Row row = new Row(); + public static TableRow getInsertOrUpdateAccountRow(boolean isInsert, int seqNum, DateType dateType, boolean insertNulls) { + ArrayList headerLabelList = new ArrayList(); + headerLabelList.add(EXT_ID_COL); + headerLabelList.add(NAME_COL); + headerLabelList.add(SFDC_ID_COL); + headerLabelList.add(ACCOUNT_NUMBER_COL); + headerLabelList.add(PHONE_COL); + headerLabelList.add(REVENUE_COL); + headerLabelList.add(LAST_UPDATED_COL); + + TableHeader header = new TableHeader(headerLabelList); + TableRow row = new TableRow(header); String operation; int seqInt; // external id is the key, use normal sequencing for update so the same set of records gets updated as inserted @@ -196,7 +212,7 @@ public static Row getInsertOrUpdateAccountRow(boolean isInsert, int seqNum, Date public static void deleteAllAccountsDb(Controller theController) throws Exception { DatabaseWriter writer = null; try { - writer = new DatabaseWriter(theController.getConfig(), "deleteAccountAll"); + writer = new DatabaseWriter(theController.getAppConfig(), "deleteAccountAll"); writer.open(); logger.info("Deleting all Accounts from database, using configuration: " + "deleteAccountAll"); writer.writeRow(null); diff --git a/src/test/java/com/salesforce/dataloader/dyna/BooleanConverterTest.java b/src/test/java/com/salesforce/dataloader/dyna/BooleanConverterTest.java index 8e937e086..495790bda 100644 --- a/src/test/java/com/salesforce/dataloader/dyna/BooleanConverterTest.java +++ b/src/test/java/com/salesforce/dataloader/dyna/BooleanConverterTest.java @@ -28,12 +28,14 @@ import org.apache.commons.beanutils.ConversionException; import org.junit.Test; +import com.salesforce.dataloader.ConfigTestBase; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -public class BooleanConverterTest { +public class BooleanConverterTest extends ConfigTestBase { private static final String[] VALID_TRUE_VALUES = {"yes", "y", "true", "on", "1"}; private static final String[] VALID_FALSE_VALUES = {"no", "n", "false", "off", "0"}; diff --git a/src/test/java/com/salesforce/dataloader/dyna/DateConverterTest.java b/src/test/java/com/salesforce/dataloader/dyna/DateConverterTest.java index ec8defec9..91806a6de 100644 --- a/src/test/java/com/salesforce/dataloader/dyna/DateConverterTest.java +++ b/src/test/java/com/salesforce/dataloader/dyna/DateConverterTest.java @@ -27,8 +27,12 @@ import org.apache.commons.beanutils.ConversionException; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.config.AppConfig; + import java.util.Calendar; import java.util.Date; import java.util.TimeZone; @@ -38,7 +42,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; -public class DateConverterTest { +public class DateConverterTest extends ConfigTestBase { private static final TimeZone TZ = TimeZone.getTimeZone("GMT"); @@ -175,6 +179,9 @@ public void testFullBasicFormat() { assertValidDate("String to calendar conversation fails when minutes not specified with non-GMT timezone", dateStringWithFullBasicTime, calDateGMT, false); assertValidDate("String to calendar conversation fails when minutes not specified with sans-T format", dateStringWithoutT, calDateGMT, false); + dateStringWithoutT = "20111015"; //yyyyMMdd + calDateGMT.set(2011, 9, 15, 0, 0, 0); + assertValidDate("String to calendar conversation fails when minutes not specified with sans-T format", dateStringWithoutT, calDateGMT, false); } /** @@ -187,7 +194,7 @@ public void testFullBasicFormat() { public void testDateConverterWithNull() { Calendar calDate; - DateConverter converter = new DateConverter(TZ); + DateTimeConverter converter = new DateTimeConverter(TZ, false); // test null and empty string calDate = (Calendar) converter.convert(null, null); @@ -207,7 +214,7 @@ public void testDateConverterWithNull() { public void testDateClosureUnderCalendarConversion() { Calendar calDate; - DateConverter converter = new DateConverter(TZ); + DateTimeConverter converter = new DateTimeConverter(TZ, false); // if we pass in a calendar, should get the same Calendar back Calendar testCalDate = Calendar.getInstance(); @@ -247,7 +254,7 @@ public void testTimeZoneIsRecognized() { * @expectedResults Assert that the calendar and the string will evaluate to the same instant. */ @Test - public void testNotInDelimeterPattern() { + public void testNotInDelimiterPattern() { // use this as the expected calendar instance Calendar expCalDate = Calendar.getInstance(TZ); @@ -320,23 +327,37 @@ public void testDegreesOfPrecisionInTimeString() { */ @Test public void testDateOnly() { - + testDateOnly(true); + testDateOnly(false); + } + + private void testDateOnly(boolean useEuropeanDateFormat) { Calendar expCalDate = Calendar.getInstance(TZ); expCalDate.clear(); - expCalDate.set(2004, 03, 29); - - assertValidDate("2004-04-29 00:00:00z", expCalDate, false); - assertValidDate("2004-04-29 00:00:00", expCalDate, false); - assertValidDate("2004-04-29 00:00", expCalDate, false); - assertValidDate("2004-04-29 00", expCalDate, false); - assertValidDate("2004-04-29", expCalDate, false); - assertValidDate("2004-04-29 ", expCalDate, false); - assertValidDate("2004-04-29T", expCalDate, false); - assertValidDate("2004-04-29Tz", expCalDate, false); + // yyyy, mm, dd + expCalDate.set(2020, 10, 05); + + assertValidDate("2020-11-05 00:00:00z", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05 00:00:00Z", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05 00:00:00", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05 00:00", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05 00", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05 ", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05T", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05Tz", expCalDate, useEuropeanDateFormat); + assertValidDate("2020-11-05TZ", expCalDate, useEuropeanDateFormat); + assertValidDate("20201105", expCalDate, useEuropeanDateFormat); + assertValidDate("20201105 ", expCalDate, useEuropeanDateFormat); + if (useEuropeanDateFormat) { + assertValidDate("05/11/2020", expCalDate, useEuropeanDateFormat); + } else { + assertValidDate("11/05/2020", expCalDate, useEuropeanDateFormat); + } //should fail - assertStringAndCalendarDoNotMatch("2004-04-29 00:00:01", expCalDate, false); + assertStringAndCalendarDoNotMatch("2020-11-05 00:00:01", expCalDate, useEuropeanDateFormat); } @@ -482,6 +503,7 @@ public void testInputStringWithoutTimeZoneInformation() { expCalDate.clear(); expCalDate.set(1984, 04 - 1, 12, 6, 34, 22); assertValidDate("1984-04-12T06:34:22", expCalDate, false); + assertValidDate("1984-04-12T08:34:22+02:00", expCalDate, false); } @@ -513,7 +535,7 @@ public void testInputDateString() { * */ @Test - public void testSlashAndNoTDelimeterFormat() { + public void testSlashAndNoTDelimiterFormat() { Calendar expCalDate = Calendar.getInstance(TZ); @@ -539,7 +561,7 @@ public void testSlashAndNoTDelimeterFormat() { * */ @Test - public void testSlashWithTimeZoneDelimeterFormat() { + public void testSlashWithTimeZoneDelimiterFormat() { TimeZone wst = TimeZone.getTimeZone("Australia/Perth"); Calendar calDateWST = Calendar.getInstance(wst); @@ -547,16 +569,16 @@ public void testSlashWithTimeZoneDelimeterFormat() { calDateWST.clear(); calDateWST.set(2009, 7 - 1, 16, 12, 14, 45); - for (String delimeter : new String[] { " ", "T" }) { + for (String delimiter : new String[] { " ", "T" }) { //vanilla cases that don't cross into different days - assertValidDate("07/16/2009" + delimeter + "12:14:45+0800", calDateWST, false); - assertValidDate("07/16/2009" + delimeter + "02:14:45-0200", calDateWST, false); // offset case - assertValidDate("07/16/2009" + delimeter + "16:14:45+1200", calDateWST, false); // offset case + assertValidDate("07/16/2009" + delimiter + "12:14:45+0800", calDateWST, false); + assertValidDate("07/16/2009" + delimiter + "02:14:45-0200", calDateWST, false); // offset case + assertValidDate("07/16/2009" + delimiter + "16:14:45+1200", calDateWST, false); // offset case //cross-day cases - assertValidDate("07/16/2009" + delimeter + "03:14:45-0100", calDateWST, false); - assertValidDate("07/16/2009" + delimeter + "12:14:45+0800", calDateWST, false); // offset case + assertValidDate("07/16/2009" + delimiter + "03:14:45-0100", calDateWST, false); + assertValidDate("07/16/2009" + delimiter + "12:14:45+0800", calDateWST, false); // offset case } } @@ -569,7 +591,7 @@ public void testSlashWithTimeZoneDelimeterFormat() { * */ @Test - public void testSlashWithTimeZoneDelimeterFormatEuropeanFormat() { + public void testSlashWithTimeZoneDelimiterFormatEuropeanFormat() { TimeZone wst = TimeZone.getTimeZone("Australia/Perth"); Calendar calDateWST = Calendar.getInstance(wst); @@ -577,16 +599,16 @@ public void testSlashWithTimeZoneDelimeterFormatEuropeanFormat() { calDateWST.clear(); calDateWST.set(2009, 7 - 1, 16, 12, 14, 45); - for (String delimeter : new String[] { " ", "T" }) { + for (String delimiter : new String[] { " ", "T" }) { //vanilla cases that don't cross into different days - assertValidDate("16/07/2009" + delimeter + "12:14:45+0800", calDateWST, true); - assertValidDate("16/07/2009" + delimeter + "02:14:45-0200", calDateWST, true); // offset case - assertValidDate("16/07/2009" + delimeter + "16:14:45+1200", calDateWST, true); // offset case + assertValidDate("16/07/2009" + delimiter + "12:14:45+0800", calDateWST, true); + assertValidDate("16/07/2009" + delimiter + "02:14:45-0200", calDateWST, true); // offset case + assertValidDate("16/07/2009" + delimiter + "16:14:45+1200", calDateWST, true); // offset case //cross-day cases - assertValidDate("16/07/2009" + delimeter + "03:14:45-0100", calDateWST, true); - assertValidDate("16/07/2009" + delimeter + "12:14:45+0800", calDateWST, true); // offset case + assertValidDate("16/07/2009" + delimiter + "03:14:45-0100", calDateWST, true); + assertValidDate("16/07/2009" + delimiter + "12:14:45+0800", calDateWST, true); // offset case } } @@ -625,34 +647,202 @@ public void testDateConverterNegative() { assertInvalidDate("20A4-11-08", null, false); } + // test user-specified timezones and date formats specified at + // https://developer.salesforce.com/docs/atlas.en-us.dataLoader.meta/dataLoader/supported_data_types.htm @Test public void testUserSpecifiedTimeZoneIsUsed() throws Exception { - DateConverter dateConverter = new DateConverter(TimeZone.getTimeZone("Asia/Tokyo")); + DateTimeConverter AsianTZDateConverter = new DateTimeConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + DateTimeConverter USTZDateConverter = new DateTimeConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + DateTimeConverter GMTTZDateConverter = new DateTimeConverter(TimeZone.getTimeZone("GMT"), false); + DateOnlyConverter AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + DateOnlyConverter USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + DateOnlyConverter GMTTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("GMT"), false); + + // DateConverter should always return the Calendar in GMT. + Calendar result = (Calendar) AsianTZDateConverter.convert(null, "6/7/2012"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); - Calendar result = (Calendar) dateConverter.convert(null, "6/7/2012"); + result = (Calendar) USTZDateConverter.convert(null, "6/7/2012"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) GMTTZDateConverter.convert(null, "6/7/2012"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("GMT"), result.getTimeZone()); + + // DateConverter should always return the Calendar in GMT. + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/22/2012"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(22, result.get(Calendar.DAY_OF_MONTH)); + + this.getController().getAppConfig().setValue(AppConfig.PROP_GMT_FOR_DATE_FIELD_VALUE, true); + AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + result = (Calendar) USTZDateOnlyConverter.convert(null, "6/22/2012"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(22, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("GMT"), result.getTimeZone()); + + AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 0:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH) + 1); + assertEquals(TimeZone.getTimeZone("GMT"), result.getTimeZone()); + + AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 02:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH) + 1); + assertEquals(TimeZone.getTimeZone("GMT"), result.getTimeZone()); + + // JST is 9 hours ahead of GMT + // Any time after 9am in Japan is the same day in GMT + AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 11:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("GMT"), result.getTimeZone()); + + AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 23:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("GMT"), result.getTimeZone()); + + this.getController().getAppConfig().setValue(AppConfig.PROP_GMT_FOR_DATE_FIELD_VALUE, false); + AsianTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("Asia/Tokyo"), false); + USTZDateOnlyConverter = new DateOnlyConverter(TimeZone.getTimeZone("America/Los_Angeles"), false); + + result = (Calendar) GMTTZDateOnlyConverter.convert(null, "6/22/2012"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(22, result.get(Calendar.DAY_OF_MONTH)); + + result = (Calendar) AsianTZDateConverter.convert(null, "6/7/2012 0:00"); assertEquals(6, result.get(Calendar.MONTH) + 1); assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); - result = (Calendar) dateConverter.convert(null, "6/7/2012 0:00"); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 04:00"); assertEquals(6, result.get(Calendar.MONTH) + 1); assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); - result = (Calendar) dateConverter.convert(null, "2012-06-07 00:00:00JST"); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 11:00"); assertEquals(6, result.get(Calendar.MONTH) + 1); assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); - result = (Calendar) dateConverter.convert(null, "2012-06-07 00:00:00JST"); + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "6/7/2012 17:00"); assertEquals(6, result.get(Calendar.MONTH) + 1); assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); - } + + result = (Calendar) USTZDateConverter.convert(null, "6/7/2012 0:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateOnlyConverter.convert(null, "6/7/2012 11:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateOnlyConverter.convert(null, "6/7/2012 23:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) AsianTZDateConverter.convert(null, "2012-06-07 00:00:00JST"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); + + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "2012-06-07 10:00:00JST"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); + + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "2012-06-07 22:00:00JST"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00PST"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00 PST"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00Pacific Standard Time"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00 Pacific Standard Time"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00GMT-08:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00 GMT-08:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00-08:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00 -08:00"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00-0800"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) USTZDateConverter.convert(null, "2012-06-07 00:00:00 -0800"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("America/Los_Angeles"), result.getTimeZone()); + + result = (Calendar) AsianTZDateConverter.convert(null, "2012-06-07 00:00:00JST"); + assertEquals(6, result.get(Calendar.MONTH) + 1); + assertEquals(7, result.get(Calendar.DAY_OF_MONTH)); + assertEquals(TimeZone.getTimeZone("Asia/Tokyo"), result.getTimeZone()); + + // Make sure the date is not changed in Japan DST + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "1948-05-01"); // JST(UTC+9) + assertEquals(5, result.get(Calendar.MONTH) + 1); + assertEquals(1, result.get(Calendar.DAY_OF_MONTH)); + + result = (Calendar) AsianTZDateOnlyConverter.convert(null, "1948-05-02"); // DST(UTC+10) + assertEquals(5, result.get(Calendar.MONTH) + 1); + assertEquals(2, result.get(Calendar.DAY_OF_MONTH)); + } private void assertValidDate(String msg, String strDate, Calendar expCalDate, boolean useEuropean) { - DateConverter converter = new DateConverter(TZ, useEuropean); + DateTimeConverter converter = new DateTimeConverter(TZ, useEuropean); Calendar calFromString = (Calendar)converter.convert(null, strDate); //converter is set to be assertNotNull(calFromString); //here, caldate is set to be in pacific time calFromString.setLenient(false); @@ -668,7 +858,7 @@ private void assertValidDate(String strDate, Calendar expCalDate, boolean useEur private void assertStringAndCalendarDoNotMatch(String strDate, Calendar expCalDate, boolean useEuropean) { - DateConverter converter = new DateConverter(TZ, useEuropean); + DateTimeConverter converter = new DateTimeConverter(TZ, useEuropean); Calendar calFromString = (Calendar)converter.convert(null, strDate); //converter is set to be assertNotNull(calFromString); //here, caldate is set to be in pacific time calFromString.setLenient(false); @@ -680,7 +870,7 @@ private void assertStringAndCalendarDoNotMatch(String strDate, Calendar expCalDa private void assertInvalidDate(String strDate, Calendar expCalDate, boolean useEuropean) { - DateConverter converter = new DateConverter(TZ, useEuropean); + DateTimeConverter converter = new DateTimeConverter(TZ, useEuropean); try { converter.convert(null, strDate); // converter is set to be Assert.fail("The conversion of an invalid string into a valid date occurred"); diff --git a/src/test/java/com/salesforce/dataloader/dyna/IntegerConverterTest.java b/src/test/java/com/salesforce/dataloader/dyna/IntegerConverterTest.java new file mode 100644 index 000000000..c0dcdeda2 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/dyna/IntegerConverterTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.dyna; + +import org.junit.Ignore; +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; +import java.util.Locale; +import static org.junit.Assert.assertEquals; + +public class IntegerConverterTest extends ConfigTestBase { + + @Test + public void testIntegerConversionInUS() throws Exception { + Locale prevSysLocale = Locale.getDefault(); + Locale.setDefault(Locale.US); + IntegerConverter converter = new IntegerConverter(); + assertEquals(10000, converter.convert(null, "10,000")); + assertEquals(10000, converter.convert(null, "10000")); + Locale.setDefault(prevSysLocale); + } + + @Test + public void testIntegerConversionInFrench() throws Exception { + Locale prevSysLocale = Locale.getDefault(); + Locale.setDefault(Locale.FRANCE); + IntegerConverter converter = new IntegerConverter(); + // as specified at https://stackoverflow.com/questions/9621322/numberformat-localization-issues + String numStr = "10 000"; + numStr = numStr.replace(' ', '\u00a0'); + // assertEquals(10000, converter.convert(null, numStr)); + assertEquals(10, converter.convert(null, "10,000")); + assertEquals(10000, converter.convert(null, "10000")); + Locale.setDefault(prevSysLocale); + } + + @Test + public void testIntegerConversionInItalian() throws Exception { + Locale prevSysLocale = Locale.getDefault(); + Locale.setDefault(Locale.ITALY); + IntegerConverter converter = new IntegerConverter(); + assertEquals(10000, converter.convert(null, "10.000")); + assertEquals(10000, converter.convert(null, "10000")); + Locale.setDefault(prevSysLocale); + }} \ No newline at end of file diff --git a/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java b/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java index 8983ce623..884a48b52 100644 --- a/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java +++ b/src/test/java/com/salesforce/dataloader/dyna/SObjectReferenceConverterTest.java @@ -26,12 +26,12 @@ package com.salesforce.dataloader.dyna; import com.salesforce.dataloader.ConfigTestBase; -import com.salesforce.dataloader.TestBase; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; import org.junit.Assert; import org.junit.Test; +import java.util.ArrayList; import java.util.Calendar; import static org.junit.Assert.assertEquals; @@ -45,7 +45,6 @@ public void testSObjectReferenceConverter() throws ConnectionException { SObjectReference ref; getController().login(); - getController().setReferenceDescribes(); // null test ref = (SObjectReference)refConverter.convert(null, null); @@ -75,14 +74,19 @@ public void testSObjectReferenceConverter() throws ConnectionException { testValidSObjectReference("12345", "Bogus", false); } - private void testValidSObjectReference(String refValue, String relationshipName, boolean expectSuccess) { + private void testValidSObjectReference(String refValue, String relationshipName, boolean expectSuccess) throws ConnectionException { SObjectReference ref = new SObjectReference(refValue); SObject sObj = new SObject(); - String fkFieldName = TestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD; + String fkFieldName = ConfigTestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD; + ArrayList lookupFields = new ArrayList(); + lookupFields.add(relationshipName + ":" + fkFieldName); + getController().setReferenceDescribes(lookupFields); try { - ref.addReferenceToSObject(getController(), sObj, ObjectField.formatAsString("Parent", - TestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD)); + // legacy formatting + ref.addReferenceToSObject(getController(), sObj, null, + new ParentIdLookupFieldFormatter(null, "Parent", + ConfigTestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD).toString()); SObject child = (SObject)sObj.getChild(relationshipName); boolean succeeded = child != null && child.getField(fkFieldName) != null && child.getField(fkFieldName) @@ -90,6 +94,17 @@ private void testValidSObjectReference(String refValue, String relationshipName, if (expectSuccess && !succeeded || !expectSuccess && succeeded) { Assert.fail(); } + + // new formatting + ref.addReferenceToSObject(getController(), sObj, null, new ParentIdLookupFieldFormatter("Account", "Parent", + ConfigTestBase.DEFAULT_ACCOUNT_EXT_ID_FIELD).toString()); + + child = (SObject)sObj.getChild(relationshipName); + succeeded = child != null && child.getField(fkFieldName) != null && child.getField(fkFieldName) + .equals(refValue); + if (expectSuccess && !succeeded || !expectSuccess && succeeded) { + Assert.fail(); + } } catch (Exception e) { if (expectSuccess) { Assert.fail(); diff --git a/src/test/java/com/salesforce/dataloader/integration/InstallTest.java b/src/test/java/com/salesforce/dataloader/integration/InstallTest.java deleted file mode 100644 index 398b98c5c..000000000 --- a/src/test/java/com/salesforce/dataloader/integration/InstallTest.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ - -package com.salesforce.dataloader.integration; - -import java.io.File; -import java.io.IOException; - -import autoitx4java.AutoItX; - -import org.testng.Assert; -import org.testng.annotations.*; - -import com.jacob.com.LibraryLoader; - -/** - * This class tests the Windows Installer. Dependencies are JACOB, AutoIt and AutoItX4Java. - * - * @author Jeff Lai - * @since 24.0 - */ -public class InstallTest { - - private AutoItX autoIt; - private static final String dataloaderName = "Data Loader"; - private static final String startMenuDirPath = System.getProperty("user.home") + File.separator + "Start Menu" - + File.separator + "Programs" + File.separator + "salesforce.com" + File.separator + dataloaderName; - private static final String desktopDirPath = System.getProperty("user.home") + File.separator + "Desktop"; - private String installPath; - - @BeforeClass - public void classSetup() { - String osArch = System.getProperty("os.arch"); - String jacobDllFileName = null; - if (osArch.equals("x86")) { - jacobDllFileName = "jacob-1.14.3-x86.dll"; - } else if (osArch.equals("amd64")) { - jacobDllFileName = "jacob-1.14.3-x64.dll"; - } else { - Assert.fail("unsupported os architecture: " + osArch); - } - File dll = new File(System.getProperty("user.home") + File.separator + ".m2" + File.separator + "repository" - + File.separator + "net" + File.separator + "sf" + File.separator + "jacob-project" + File.separator - + "jacob" + File.separator + "1.14.3" + File.separator + jacobDllFileName); - System.setProperty(LibraryLoader.JACOB_DLL_PATH, dll.getAbsolutePath()); - autoIt = new AutoItX(); - } - - @Test - public void testRunInstaller() throws InterruptedException { - String installerPath = System.getProperty("basedir") + File.separator + "target" + File.separator - + System.getProperty("exeName"); - Assert.assertTrue(new File(installerPath).exists(), "dataloader installer does not exist at " + installerPath); - autoIt.run(installerPath); - String dataloaderSetup = dataloaderName + " Setup"; - autoIt.winActivate(dataloaderSetup); - autoIt.winWaitActive(dataloaderSetup); - Assert.assertTrue(autoIt.winExists(dataloaderSetup), "dataloader setup window not found"); - boolean click = autoIt.controlClick(dataloaderSetup, "I &Agree", "1"); - Assert.assertTrue(click, "failed to click the I Agree button"); - click = autoIt.controlClick(dataloaderSetup, "&Next >", "1"); - Assert.assertTrue(click, "failed to click on the Next button"); - String fullWinText = autoIt.winGetText(dataloaderSetup); - installPath = getInstallPathFromWinText(fullWinText); - click = autoIt.controlClick(dataloaderSetup, "&Install", "1"); - Assert.assertTrue(click, "failed to click on the Install button"); - waitForControlActive(dataloaderSetup, "&Close", "1", 120000); - click = autoIt.controlClick(dataloaderSetup, "&Close", "1"); - Thread.sleep(3000L); - Assert.assertTrue(click, "failed to click on the Close button"); - Assert.assertFalse(autoIt.winExists(dataloaderSetup), "dataloader setup window not closed"); - } - - public String getInstallPathFromWinText(String fullWinText) { - String[] lines = fullWinText.split("\n"); - for (String line : lines) { - if (line.contains("salesforce.com\\")) return line; - } - return null; - } - - @DataProvider(name = "dataloaderShortcutPaths") - public String[][] dataloaderShortcutPaths() { - return new String[][] { { startMenuDirPath + File.separator + dataloaderName + ".lnk" }, - { desktopDirPath + File.separator + dataloaderName + ".lnk" } }; - } - - @Test(dependsOnMethods = { "testRunInstaller" }, groups = { "shortcut" }, dataProvider = "dataloaderShortcutPaths") - public void testDataloaderShortcut(String shortcutPath) throws IOException, InterruptedException { - Assert.assertTrue(new File(shortcutPath).exists(), "dataloader start menu shortcut does not exist at " - + shortcutPath); - openShortcut(shortcutPath); - Thread.sleep(3000L); - String welcomeWindow = "[CLASS:#32770]"; - String dataloaderWindow = "[CLASS:SWT_Window0]"; - String welcomeWindowHandle = autoIt.winGetHandle(welcomeWindow); - Assert.assertTrue(autoIt.winExists(welcomeWindow), "dataloader welcome window not found"); - Assert.assertTrue(autoIt.winExists(dataloaderWindow), "dataloader window not found"); - boolean click = autoIt.controlClick(welcomeWindow, "Cancel", "[CLASS:Button; INSTANCE:8]"); - Assert.assertTrue(click, "failed to click the Cancel button"); - Thread.sleep(3000L); - autoIt.winClose(dataloaderWindow); - Thread.sleep(3000L); - Assert.assertFalse(autoIt.winExists(welcomeWindowHandle), "dataloader welcome window not closed"); - Assert.assertFalse(autoIt.winExists(dataloaderWindow), "dataloader window not closed"); - } - - @Test(dependsOnMethods = { "testRunInstaller" }, groups = { "shortcut" }) - public void testUninstallShortcut() throws IOException, InterruptedException { - String dataloaderUninstall = openUninstaller(); - autoIt.winClose(dataloaderUninstall); - Thread.sleep(3000L); - Assert.assertFalse(autoIt.winExists(dataloaderUninstall), "dataloader uninstall window not closed"); - } - - @Test(dependsOnGroups = { "shortcut" }) - public void testRunUninstaller() throws IOException, InterruptedException { - String dataloaderUninstall = openUninstaller(); - boolean click = autoIt.controlClick(dataloaderUninstall, "&Next >", "1"); - Assert.assertTrue(click, "failed to click on the Next button"); - click = autoIt.controlClick(dataloaderUninstall, "&Uninstall", "1"); - Assert.assertTrue(click, "failed to click on the Uninstall button"); - waitForControlActive(dataloaderUninstall, "&Close", "1", 120000); - click = autoIt.controlClick(dataloaderUninstall, "&Close", "1"); - Thread.sleep(3000L); - Assert.assertTrue(click, "failed to click on the Close button"); - Assert.assertFalse(autoIt.winExists(dataloaderUninstall), "dataloader uninstall window not closed"); - Assert.assertFalse(new File(startMenuDirPath).exists(), "start menu shortcuts not deleted"); - Assert.assertFalse(new File(desktopDirPath + File.separator + "Dataloader.lnk").exists(), - "desktop shortcut not deleted"); - Assert.assertFalse(new File(installPath).exists(), "program files directory not deleted"); - } - - private String openUninstaller() throws IOException { - String shortcutPath = startMenuDirPath + File.separator + "Uninstall " + dataloaderName + ".lnk"; - Assert.assertTrue(new File(shortcutPath).exists(), "uninstall start menu shortcut does not exist at " - + shortcutPath); - openShortcut(shortcutPath); - String dataloaderUninstall = dataloaderName + " Uninstall"; - autoIt.winActivate(dataloaderUninstall); - autoIt.winWaitActive(dataloaderUninstall); - Assert.assertTrue(autoIt.winExists(dataloaderUninstall), "dataloader uninstall window not found"); - return dataloaderUninstall; - } - - private void openShortcut(String shortcutPath) throws IOException { - Runtime.getRuntime().exec("cmd /c \"" + shortcutPath + "\""); - } - - private void waitForControlActive(String title, String text, String control, long maxWaitTime) - throws InterruptedException { - long timeWaited = 0; - while (true) { - if (timeWaited >= maxWaitTime) { - Assert.fail("waited " + timeWaited + " milliseconds but control with id " + control + " is not active"); - } else if (autoIt.controlCommandIsEnabled(title, text, control)) { - break; - } else { - Thread.sleep(2000L); - timeWaited += 2000L; - } - } - } - -} diff --git a/src/test/java/com/salesforce/dataloader/mapping/LoadMapperTest.java b/src/test/java/com/salesforce/dataloader/mapping/LoadMapperTest.java index fcdc63a2e..e3af24550 100644 --- a/src/test/java/com/salesforce/dataloader/mapping/LoadMapperTest.java +++ b/src/test/java/com/salesforce/dataloader/mapping/LoadMapperTest.java @@ -28,13 +28,20 @@ import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.exception.MappingInitializationException; import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.sforce.soap.partner.Field; +import com.sforce.soap.partner.FieldType; + import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.io.File; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -49,6 +56,7 @@ * @author Federico Recio * @since 8.0 */ +@SuppressWarnings("unused") public class LoadMapperTest extends ConfigTestBase { private static final String[] SOURCE_NAMES = { "sourceOne", "sourceTwo", "sourceThree" }; @@ -56,11 +64,13 @@ public class LoadMapperTest extends ConfigTestBase { private static final String[] DEST_NAMES = { "destinationOne", "destinationTwo", "destinationThree" }; private static final String DEST_CONSTANT_NAME = "destinationConstant"; private static final String CONSTANT_VALUE = "constantValue123"; - private Row sourceRow; + private TableRow sourceRow; @Before public void setUp() throws Exception { - sourceRow = new Row(); + List headerList = Arrays.asList(SOURCE_NAMES); + TableHeader tableHeader = new TableHeader(headerList); + sourceRow = new TableRow(tableHeader); // populate all the available values for (int i = 0; i < SOURCE_NAMES.length; i++) { sourceRow.put(SOURCE_NAMES[i], SOURCE_VALUES[i]); @@ -146,7 +156,7 @@ public void testDuplicateConstants() throws MappingInitializationException { mappings.setProperty("", "Value6"); LoadMapper mapper = new LoadMapper(null, null, null, null); mapper.putPropertyFileMappings(mappings); - Row result = mapper.mapData(Row.emptyRow()); + TableRow result = mapper.mapData(TableRow.emptyRow(), true); assertEquals(constantValue, result.get("Name")); assertEquals(constantValue, result.get("field1__c")); assertEquals(constantValue, result.get("field2__c")); @@ -188,11 +198,11 @@ public void testColumnValueDoesNotOverrideConstant() throws MappingInitializatio mapper.putPropertyFileMappings(mappings); //place a dao column -> sfdc field mapping - Row input = Row.singleEntryImmutableRow(csvFieldName, sfdcField); + TableRow input = TableRow.singleEntryImmutableRow(csvFieldName, sfdcField); //(src, dest). mapper.putMapping(csvFieldName, sfdcField); - Map result = mapper.mapData(input); + TableRow result = mapper.mapData(input, true); //verify that the old value holds assertEquals(constantValue, result.get(sfdcField)); @@ -225,9 +235,9 @@ public void testConstValueOverridesColumnValue() throws Exception { mappings.setProperty(wrappedConstantValue, sfdcField); mapper.putPropertyFileMappings(mappings); - Row input = Row.singleEntryImmutableRow(constantValue, sfdcField); + TableRow input = TableRow.singleEntryImmutableRow(constantValue, sfdcField); - Map result = mapper.mapData(input); + TableRow result = mapper.mapData(input, true); //verify that the old value holds assertEquals(constantValue, result.get(sfdcField)); @@ -238,11 +248,10 @@ public void testMapDataEmptyEntriesIgnored() throws Exception { LoadMapper loadMapper = new LoadMapper(null, null, null, null); loadMapper.putMapping("SOURCE_COL", ""); - Row inputData = Row.singleEntryImmutableRow("SOURCE_COL", "123"); - - Map result = loadMapper.mapData(inputData); + TableRow inputData = TableRow.singleEntryImmutableRow("SOURCE_COL", "123"); - assertTrue("Empty destination column should have not been mapped", result.isEmpty()); + TableRow result = loadMapper.mapData(inputData, true); + assertTrue("Empty destination column should have not been mapped", result.getNonEmptyCellsCount() == 0); } @Test @@ -263,12 +272,29 @@ public void testVerifyMappingsAreValidUnknownColumn() throws Exception { // expected } } - + + @Test + public void testCompositeMap() throws Exception { + Field[] fields = new Field[3]; + for (int i = 0; i < 3; i++) { + fields[i] = new Field(); + fields[i].setName(DEST_NAMES[i]); + fields[i].setType(FieldType.string); + } + LoadMapper loadMapper = new LoadMapper(getController().getPartnerClient(), null, fields, null); + loadMapper.putMapping(SOURCE_NAMES[0] + "," + SOURCE_NAMES[1], + DEST_NAMES[0] + "," + DEST_NAMES[1]); + TableRow destValueRow = loadMapper.mapData(this.sourceRow, true); + String expectedDestValue = this.sourceRow.get(SOURCE_NAMES[0]) + ", " + this.sourceRow.get(SOURCE_NAMES[1]); + assertEquals(destValueRow.get(DEST_NAMES[0]), expectedDestValue); + assertEquals(destValueRow.get(DEST_NAMES[1]), expectedDestValue); + } + /** * Helper method to verify that the LoadMapper has mapped the specified columns to their correct respective field, along with any constants in the mapping file. */ private void verifyMapping(LoadMapper mapper, String... destNames) { - Row destValueMap = mapper.mapData(this.sourceRow); + TableRow destValueMap = mapper.mapData(this.sourceRow, true); for (int i = 0; i < destNames.length; i++) { assertNotNull("Destination# " + i + "(" + destNames[i] + ") should have a mapped value", diff --git a/src/test/java/com/salesforce/dataloader/mapping/SOQLMapperTest.java b/src/test/java/com/salesforce/dataloader/mapping/SOQLMapperTest.java index e5e7b46a3..31a928b45 100644 --- a/src/test/java/com/salesforce/dataloader/mapping/SOQLMapperTest.java +++ b/src/test/java/com/salesforce/dataloader/mapping/SOQLMapperTest.java @@ -28,10 +28,11 @@ import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.client.PartnerClient; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.exception.MappingInitializationException; import com.sforce.ws.ConnectionException; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import java.util.Arrays; @@ -46,6 +47,7 @@ * @author Federico Recio * @since 27.0 */ +@SuppressWarnings("unused") public class SOQLMapperTest extends ConfigTestBase { private SOQLMapper soqlMapper; @@ -58,7 +60,7 @@ public void createSoqlMapper() throws Exception { @Test public void testRelationshipQuery() throws Exception { - getController().getConfig().setValue(Config.ENTITY, "User"); + getController().getAppConfig().setValue(AppConfig.PROP_ENTITY, "User"); soqlMapper.initSoqlMapping("select Id, Contact.Accountid from User"); List daoColumnsForSoql = soqlMapper.getDaoColumnsForSoql(); @@ -100,12 +102,22 @@ public void testMapAutoMatch() throws Exception { private void doAutoMatchTest(String soql) throws ConnectionException, MappingInitializationException { getController().login(); - List daoCols = Arrays.asList("NAME", "ID", "Parent.Id", + List possibleMappingDaoCols = Arrays.asList("NAME", "ID", "Parent.Id", "NumberOfEMPLOYEES"); SOQLMapper mapper = new SOQLMapper(getController().getPartnerClient(), - daoCols, null, null); + possibleMappingDaoCols, null, null); mapper.initSoqlMapping(soql); - List actual = mapper.getDaoColumnsForSoql(); - assertEquals(daoCols, actual); + List colsInSoql = mapper.getDaoColumnsForSoql(); + int numMappedColsInSoql = 0; + for (String possibleMappingDaoCol : possibleMappingDaoCols) { + for (String colInSoql : colsInSoql) { + if (colInSoql.equalsIgnoreCase(possibleMappingDaoCol)) { + numMappedColsInSoql++; + break; + } + } + } + assertTrue("actual mappings in the soql is less than possible mappings", + numMappedColsInSoql <= possibleMappingDaoCols.size()); } } diff --git a/src/test/java/com/salesforce/dataloader/mapping/SoqlInfoTest.java b/src/test/java/com/salesforce/dataloader/mapping/SoqlInfoTest.java index 985bf2b14..f3f94b145 100644 --- a/src/test/java/com/salesforce/dataloader/mapping/SoqlInfoTest.java +++ b/src/test/java/com/salesforce/dataloader/mapping/SoqlInfoTest.java @@ -26,6 +26,7 @@ package com.salesforce.dataloader.mapping; +import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.mapping.SOQLInfo.SOQLFieldInfo; import com.salesforce.dataloader.mapping.SOQLInfo.SOQLParserException; import org.junit.Assert; @@ -42,7 +43,7 @@ * @author Colin Jarvis * @since 21.0 */ -public class SoqlInfoTest { +public class SoqlInfoTest extends ConfigTestBase { @Test public void testParseSoql() throws SOQLParserException { @@ -175,7 +176,7 @@ public void testNoFrom() { @Test public void testNestedQuery() { runInvalidQueryTest("select id, (select name from contacts) from account blarney", - "Nested queries are not supported"); + "Nested queries are not supported in SOQL SELECT clause"); } @Test diff --git a/src/test/java/com/salesforce/dataloader/model/OAuthTokenTests.java b/src/test/java/com/salesforce/dataloader/model/OAuthTokenTests.java index 1abb8af7d..665b562a2 100644 --- a/src/test/java/com/salesforce/dataloader/model/OAuthTokenTests.java +++ b/src/test/java/com/salesforce/dataloader/model/OAuthTokenTests.java @@ -25,13 +25,15 @@ */ package com.salesforce.dataloader.model; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.Assert; +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; /** * Created by rmazzeo on 12/9/15. */ -public class OAuthTokenTests { +public class OAuthTokenTests extends ConfigTestBase { @Test public void testAccessToken(){ OAuthToken target = new OAuthToken(); @@ -40,7 +42,7 @@ public void testAccessToken(){ target.setAccessToken(expected); String actual = target.getAccessToken(); - Assert.assertEquals(actual, expected, "Access tokens differed"); + Assert.assertEquals("Access tokens differed", actual, expected); } @Test @@ -51,7 +53,7 @@ public void testTokenType(){ target.setTokenType(expected); String actual = target.getTokenType(); - Assert.assertEquals(actual, expected, "Token type differed"); + Assert.assertEquals("Token type differed", actual, expected); } @Test @@ -62,7 +64,7 @@ public void testSignature(){ target.setSignature(expected); String actual = target.getSignature(); - Assert.assertEquals(actual, expected, "Signature differed"); + Assert.assertEquals("Signature differed", actual, expected); } @Test @@ -73,7 +75,7 @@ public void testInstanceUrl(){ target.setInstanceUrl(expected); String actual = target.getInstanceUrl(); - Assert.assertEquals(actual, expected, "Instance Url differed"); + Assert.assertEquals("Instance Url differed", actual, expected); } @Test @@ -84,7 +86,7 @@ public void testScope(){ target.setScope(expected); String actual = target.getScope(); - Assert.assertEquals(actual, expected, "Scope differed"); + Assert.assertEquals("Scope differed", actual, expected); } @Test @@ -95,7 +97,7 @@ public void testId(){ target.setId(expected); String actual = target.getId(); - Assert.assertEquals(actual, expected, "Id differed"); + Assert.assertEquals("Id differed", actual, expected); } @Test @@ -106,7 +108,7 @@ public void testIssuedAt(){ target.setIssuedAt(expected); Long actual = target.getIssuedAt(); - Assert.assertEquals(actual, expected, "IssuedAt differed"); + Assert.assertEquals("IssuedAt differed", actual, expected); } @Test @@ -117,6 +119,6 @@ public void testRefreshToken(){ target.setRefreshToken(expected); String actual = target.getRefreshToken(); - Assert.assertEquals(actual, expected, "RefreshToken differed"); + Assert.assertEquals("RefreshToken differed", actual, expected); } } diff --git a/src/test/java/com/salesforce/dataloader/oauth/OAuthFlowUtilTests.java b/src/test/java/com/salesforce/dataloader/oauth/OAuthFlowUtilTests.java new file mode 100644 index 000000000..3c969e15e --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/oauth/OAuthFlowUtilTests.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.oauth; + +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.config.AppConfig; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.UnsupportedEncodingException; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; + +/** + * test the oauth token flow as best we can. sadly there is SWT here which isn't very testable for unit tests + */ +public class OAuthFlowUtilTests extends ConfigTestBase { + + private AppConfig appConfig; + private ArrayList existingOAuthEnvironments; + private String oauthServer; + private String oauthClientId; + private String oauthRedirectUri; + private String existingEndPoint; + + @Before + public void testSetup(){ + appConfig = getController().getAppConfig(); + existingOAuthEnvironments = appConfig.getStrings(AppConfig.PROP_SERVER_ENVIRONMENTS); + existingEndPoint = appConfig.getAuthEndpointForCurrentEnv(); + oauthServer = "https://OAUTH_PARTIAL_SERVER"; + oauthClientId = "CLIENTID"; + oauthRedirectUri = "REDIRECTURI"; + + appConfig.setValue(AppConfig.PROP_SERVER_ENVIRONMENTS, "Testing"); + appConfig.setOAuthEnvironmentString("Testing", AppConfig.CLIENTID_LITERAL, oauthClientId); + appConfig.setOAuthEnvironmentString("Testing", AppConfig.REDIRECTURI_LITERAL, oauthRedirectUri); + appConfig.setServerEnvironment("Testing"); + } + + @After + public void testCleanup(){ + appConfig.setValue(AppConfig.PROP_SERVER_ENVIRONMENTS, existingOAuthEnvironments.toArray(new String[0])); + appConfig.setAuthEndpointForCurrentEnv(existingEndPoint); + } + + @Test + public void testGetStartUrl(){ + try { + String expected = appConfig.getAuthEndpointForCurrentEnv() + "/services/oauth2/authorize" + + "?response_type=token" + + "&display=popup" + + "&" + appConfig.getClientIdNameValuePair() + + "&redirect_uri=" + + URLEncoder.encode(appConfig.getAuthEndpointForCurrentEnv() + + "services/oauth2/success", StandardCharsets.UTF_8.name()); + String actual = OAuthFlowUtil.getStartUrlImpl(appConfig); + + Assert.assertEquals( "OAuth Token Flow returned the wrong url", expected, actual); + } catch (UnsupportedEncodingException e) { + Assert.fail("could not get start url" + e.toString()); + } + } + + @Test + public void testInvalidReponseUrl(){ + try { + Boolean condition = OAuthFlowUtil.handleCompletedUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?doit=1", appConfig); + Assert.assertFalse("OAuthToken should not have handled this", condition); + + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } + + @Test + public void testValidResponseUrl(){ + try { + Boolean condition = OAuthFlowUtil.handleCompletedUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN&instance_url=https://INSTANCEURL", appConfig); + Assert.assertTrue("OAuthToken should have handled this", condition); + + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } + + @Test + public void testValidResponseUrlSetsAccessToken(){ + try { + OAuthFlowUtil.handleCompletedUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN&instance_url=https://INSTANCEURL", appConfig); + String expected = "TOKEN"; + String actual = appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN); + + Assert.assertEquals("Incorrect access token found in config", expected, actual); + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } + + @Test + public void testValidResponseUrlSetsRefreshToken(){ + try { + OAuthFlowUtil.handleCompletedUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN&refresh_token=REFRESHTOKEN&instance_url=https://INSTANCEURL", appConfig); + String expected = "REFRESHTOKEN"; + String actual = appConfig.getString(AppConfig.PROP_OAUTH_REFRESHTOKEN); + + Assert.assertEquals("Incorrect refresh token found in config", expected, actual); + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } + + @Test + public void testValidResponseUrlSetsEndPoint(){ + try { + OAuthFlowUtil.handleCompletedUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN&instance_url=https://INSTANCEURL", appConfig); + String expected = "https://INSTANCEURL"; + String actual = appConfig.getString(AppConfig.PROP_OAUTH_INSTANCE_URL); + + Assert.assertEquals("Incorrect refresh token found in config", expected, actual); + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } +} diff --git a/src/test/java/com/salesforce/dataloader/oauth/OAuthSecretFlowUtilTests.java b/src/test/java/com/salesforce/dataloader/oauth/OAuthSecretFlowUtilTests.java new file mode 100644 index 000000000..82daa9fa3 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/oauth/OAuthSecretFlowUtilTests.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.oauth; + +import com.google.gson.FieldNamingPolicy; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.client.SimplePost; +import com.salesforce.dataloader.client.SimplePostFactory; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.exception.ParameterLoadException; +import com.salesforce.dataloader.model.OAuthToken; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import static org.mockito.Mockito.*; + +import java.io.*; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.function.Function; + +/** + * Created by rmazzeo on 12/9/15. + */ +public class OAuthSecretFlowUtilTests extends ConfigTestBase { + + private SimplePost mockSimplePost; + private AppConfig appConfig; + private ArrayList existingOAuthEnvironments; + private String oauthServer; + private String oauthClientId; + private String oauthRedirectUri; + private String existingEndPoint; + private Function existingConstructor; + + @Before + public void testSetup(){ + appConfig = getController().getAppConfig(); + existingOAuthEnvironments = appConfig.getStrings(AppConfig.PROP_SERVER_ENVIRONMENTS); + existingEndPoint = appConfig.getAuthEndpointForCurrentEnv(); + oauthServer = "https://OAUTH_PARTIAL_SERVER"; + oauthClientId = "CLIENTID"; + oauthRedirectUri = "https://REDIRECTURI"; + mockSimplePost = mock(SimplePost.class); + + appConfig.setValue(AppConfig.PROP_SERVER_ENVIRONMENTS, "Testing"); + appConfig.setOAuthEnvironmentString("Testing", AppConfig.CLIENTID_LITERAL, oauthClientId); + appConfig.setOAuthEnvironmentString("Testing", AppConfig.REDIRECTURI_LITERAL, oauthRedirectUri); + appConfig.setServerEnvironment("Testing"); + + existingConstructor = SimplePostFactory.getConstructor(); + SimplePostFactory.setConstructor(c -> mockSimplePost); + } + + @After + public void testCleanup(){ + appConfig.setValue(AppConfig.PROP_SERVER_ENVIRONMENTS, existingOAuthEnvironments.toArray(new String[0])); + appConfig.setAuthEndpointForCurrentEnv(existingEndPoint); + SimplePostFactory.setConstructor(existingConstructor); + } + + @Test + public void testGetStartUrl(){ + try { + String expected = appConfig.getAuthEndpointForCurrentEnv() + "/services/oauth2/authorize" + + "?response_type=code" + + "&display=popup" + + "&" + appConfig.getClientIdNameValuePair() + + "&" + "redirect_uri=" + + URLEncoder.encode(appConfig.getAuthEndpointForCurrentEnv() + + "services/oauth2/success", StandardCharsets.UTF_8.name()); + String actual = OAuthSecretFlowUtil.getStartUrlImpl(appConfig); + + Assert.assertEquals( "OAuth Token Flow returned the wrong url", expected, actual); + } catch (UnsupportedEncodingException e) { + Assert.fail("could not get start url" + e.toString()); + } + } + + @Test + public void testInvalidInitialReponseUrl(){ + try { + String expected = null; + String actual = OAuthSecretFlowUtil.handleInitialUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?doit=1"); + Assert.assertEquals("OAuthToken should not have handled this", expected, actual); + + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } + + @Test + public void testValidInitialResponseUrl(){ + try { + String expected = "TOKEN"; + String actual = OAuthSecretFlowUtil.handleInitialUrl( "https://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?code=TOKEN&instance_url=https://INSTANCEURL"); + Assert.assertEquals("OAuthToken should not have handled this", expected, actual); + + } catch (URISyntaxException e) { + Assert.fail("Could not handle the url:" + e.toString()); + } + } + + @Test + public void testValidSecondResponseAccessToken(){ + try { + + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + OAuthToken token = new OAuthToken(); + token.setAccessToken("ACCESS"); + token.setInstanceUrl("https://INSTANCEURL"); + String jsonToken = gson.toJson(token); + InputStream input = new ByteArrayInputStream(jsonToken.getBytes(StandardCharsets.UTF_8)); + when(mockSimplePost.getInput()).thenAnswer(i -> input); + when(mockSimplePost.isSuccessful()).thenReturn(true); + + @SuppressWarnings("unused") + SimplePost simplePost = OAuthSecretFlowUtil.handleSecondPost("simplePost", appConfig); + + String expected = "ACCESS"; + String actual = appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN); + when(mockSimplePost.isSuccessful()).thenReturn(true); + + Assert.assertEquals("Access token was not set", expected, actual); + + } catch (ParameterLoadException | IOException e) { + Assert.fail("Could not handle second request:" + e.toString()); + } + } + + @Test + public void testValidSecondResponseRefreshToken(){ + try { + + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + OAuthToken token = new OAuthToken(); + token.setRefreshToken("REFRESHTOKEN"); + token.setInstanceUrl("https://INSTANCEURL"); + String jsonToken = gson.toJson(token); + InputStream input = new ByteArrayInputStream(jsonToken.getBytes(StandardCharsets.UTF_8)); + when(mockSimplePost.getInput()).thenAnswer(i -> input); + when(mockSimplePost.isSuccessful()).thenReturn(true); + + @SuppressWarnings("unused") + SimplePost simplePost = OAuthSecretFlowUtil.handleSecondPost("simplePost", appConfig); + + String expected = "REFRESHTOKEN"; + String actual = appConfig.getString(AppConfig.PROP_OAUTH_REFRESHTOKEN); + + Assert.assertEquals("Access token was not set", expected, actual); + + } catch (ParameterLoadException | IOException e) { + Assert.fail("Could not handle second request:" + e.toString()); + } + } + + @Test + public void testValidSecondResponseInstanceUrl(){ + try { + + Gson gson = new GsonBuilder() + .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) + .create(); + OAuthToken token = new OAuthToken(); + token.setInstanceUrl("https://INSTANCEURL"); + String jsonToken = gson.toJson(token); + InputStream input = new ByteArrayInputStream(jsonToken.getBytes(StandardCharsets.UTF_8)); + when(mockSimplePost.getInput()).thenAnswer(i -> input); + when(mockSimplePost.isSuccessful()).thenReturn(true); + + @SuppressWarnings("unused") + SimplePost simplePost = OAuthSecretFlowUtil.handleSecondPost("simplePost", appConfig); + + String expected = "https://INSTANCEURL"; + String actual = appConfig.getString(AppConfig.PROP_OAUTH_INSTANCE_URL);; + + Assert.assertEquals("Access token was not set", expected, actual); + + } catch (ParameterLoadException | IOException e) { + Assert.fail("Could not handle second request:" + e.toString()); + } + } +} diff --git a/src/test/java/com/salesforce/dataloader/process/BulkCsvProcessTest.java b/src/test/java/com/salesforce/dataloader/process/BulkCsvProcessTest.java deleted file mode 100644 index 9ccf51a9d..000000000 --- a/src/test/java/com/salesforce/dataloader/process/BulkCsvProcessTest.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.process; - -import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Map; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; - -import com.salesforce.dataloader.TestProgressMontitor; -import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.controller.Controller; -import com.salesforce.dataloader.dao.csv.CSVFileWriter; -import com.salesforce.dataloader.model.Row; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -public class BulkCsvProcessTest extends ProcessTestBase { - - private static final String TASK_SUBJECT = "BulkCsvProcessTest"; - private static final String TARGET_DIR = getProperty("target.dir").trim(); - private static final String CSV_DIR_PATH = TARGET_DIR + File.separator + "BatchTests"; - private static final String CSV_FILE_PATH = CSV_DIR_PATH + File.separator + "BatchTests.csv"; - private static Row validRow; - private static Row invalidRow; - private Map argMap; - - @BeforeClass - public static void setUpData() { - validRow = new Row(); - validRow.put("Subject", TASK_SUBJECT); - validRow.put("ReminderDateTime", ""); - - invalidRow = new Row(); - invalidRow.put("Subject", TASK_SUBJECT); - invalidRow.put("ReminderDateTime", "NULL"); // this makes date conversion fail - } - - @Before - public void createArgMap() { - argMap = getTestConfig(OperationInfo.insert, CSV_FILE_PATH, getTestDataDir() + File.separator + "NAProcessTest.sdl", false); - argMap.put(Config.ENTITY, "Task"); - argMap.remove(Config.EXTERNAL_ID_FIELD); - argMap.put(Config.BULK_API_ENABLED, Boolean.TRUE.toString()); - } - - @Override - public void cleanRecords() { - deleteSfdcRecords("Task", "Subject='" + TASK_SUBJECT + "'", 0); - } - - @Test - public void testBatchSizes() throws Exception { - writeCsv(validRow, validRow); - argMap.put(Config.LOAD_BATCH_SIZE, "1"); - TestProgressMontitor monitor = runProcess(argMap, 2, 0, 0, false); - assertEquals("Inserting 2 rows with batch size of 1 should have produced 2 batches", 2, monitor.getNumberBatchesTotal()); - } - - @Test - public void testBatchSizesNotAlteredByInvalidData() throws Exception { - writeCsv(validRow, invalidRow, validRow); - argMap.put(Config.LOAD_BATCH_SIZE, "2"); - TestProgressMontitor monitor = runProcess(argMap, 2, 0, 1, false); - assertEquals("Even though middle row contains invalid data only 1 batch should have been created", 1, monitor.getNumberBatchesTotal()); - } - - private TestProgressMontitor runProcess(Map argMap, int numInserts, int numUpdates, int numFailures, boolean emptyId) throws Exception { - final ProcessRunner runner = ProcessRunner.getInstance(argMap); - runner.setName(baseName); - - final TestProgressMontitor monitor = new TestProgressMontitor(); - runner.run(monitor); - Controller controller = runner.getController(); - - assertTrue("Process failed", monitor.isSuccess()); - verifyFailureFile(controller, numFailures); - verifySuccessFile(controller, numInserts, numUpdates, emptyId); - return monitor; - } - - private void writeCsv(Row... rows) throws Exception { - File csvDir = new File(CSV_DIR_PATH); - if (!csvDir.exists()) { - boolean deleteCsvDirOk = csvDir.mkdirs(); - assertTrue("Could not delete directory: " + CSV_DIR_PATH, deleteCsvDirOk); - } - File csvFile = new File(CSV_FILE_PATH); - if (csvFile.exists()) { - boolean deleteCsvFileOk = csvFile.delete(); - assertTrue("Could not delete existing CSV file: " + CSV_FILE_PATH, deleteCsvFileOk); - } - - CSVFileWriter writer = null; - try { - writer = new CSVFileWriter(CSV_FILE_PATH, getController().getConfig()); - writer.open(); - writer.setColumnNames(new ArrayList(rows[0].keySet())); - writer.writeRowList(Arrays.asList(rows)); - } finally { - if (writer != null) { - writer.close(); - } - } - } -} diff --git a/src/test/java/com/salesforce/dataloader/process/BulkV1CsvProcessTest.java b/src/test/java/com/salesforce/dataloader/process/BulkV1CsvProcessTest.java new file mode 100644 index 000000000..c21a916a2 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/process/BulkV1CsvProcessTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.process; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Map; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.csv.CSVFileWriter; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class BulkV1CsvProcessTest extends ProcessTestBase { + + private static final String TASK_SUBJECT = "BulkV1CsvProcessTest"; + private static final String TARGET_DIR = getProperty("target.dir").trim(); + private static final String CSV_DIR_PATH = TARGET_DIR + File.separator + "BatchTests"; + private static final String CSV_FILE_PATH = CSV_DIR_PATH + File.separator + "BatchTests.csv"; + private static TableRow validRow; + private static TableRow invalidRow; + private Map argMap; + + @BeforeClass + public static void setUpData() { + ArrayList headerNames = new ArrayList(); + headerNames.add("Subject"); + headerNames.add("ReminderDateTime"); + TableHeader header = new TableHeader(headerNames); + validRow = new TableRow(header); + validRow.put("Subject", TASK_SUBJECT); + validRow.put("ReminderDateTime", ""); + + invalidRow = new TableRow(header); + invalidRow.put("Subject", TASK_SUBJECT); + invalidRow.put("ReminderDateTime", "NULL"); // this makes date conversion fail + } + + @Before + public void createArgMap() { + argMap = getTestConfig(OperationInfo.insert, CSV_FILE_PATH, getTestDataDir() + File.separator + "NAProcessTest.sdl", false); + argMap.put(AppConfig.PROP_ENTITY, "Task"); + argMap.remove(AppConfig.PROP_IDLOOKUP_FIELD); + argMap.put(AppConfig.PROP_BULK_API_ENABLED, Boolean.TRUE.toString()); + } + + @Test + public void testBatchSizes() throws Exception { + writeCsv(validRow, validRow); + argMap.put(AppConfig.PROP_IMPORT_BATCH_SIZE, "1"); + ILoaderProgress monitor = runProcess(argMap, 2, 0, 0, false); + assertEquals("Inserting 2 rows with batch size of 1 should have produced 2 batches", 2, monitor.getNumberBatchesTotal()); + } + + @Test + public void testBatchSizesNotAlteredByInvalidData() throws Exception { + writeCsv(validRow, invalidRow, validRow); + argMap.put(AppConfig.PROP_IMPORT_BATCH_SIZE, "2"); + ILoaderProgress monitor = runProcess(argMap, 2, 0, 1, false); + assertEquals("Even though middle row contains invalid data only 1 batch should have been created", 1, monitor.getNumberBatchesTotal()); + } + + private ILoaderProgress runProcess(Map argMap, int numInserts, int numUpdates, int numFailures, boolean emptyId) throws Exception { + + final IProcess runner = this.runBatchProcess(argMap); + ILoaderProgress monitor = runner.getMonitor(); + Controller controller = runner.getController(); + + assertTrue("Process failed", monitor.isSuccess()); + verifyFailureFile(controller, numFailures); + verifySuccessFile(controller, numInserts, numUpdates, emptyId); + return monitor; + } + + private void writeCsv(TableRow... rows) throws Exception { + File csvDir = new File(CSV_DIR_PATH); + if (!csvDir.exists()) { + boolean deleteCsvDirOk = csvDir.mkdirs(); + assertTrue("Could not delete directory: " + CSV_DIR_PATH, deleteCsvDirOk); + } + File csvFile = new File(CSV_FILE_PATH); + if (csvFile.exists()) { + boolean deleteCsvFileOk = csvFile.delete(); + assertTrue("Could not delete existing CSV file: " + CSV_FILE_PATH, deleteCsvFileOk); + } + + CSVFileWriter writer = null; + try { + writer = new CSVFileWriter(CSV_FILE_PATH, getController().getAppConfig(), AppUtil.COMMA); + writer.open(); + writer.setColumnNames(rows[0].getHeader().getColumns()); + writer.writeRowList(Arrays.asList(rows)); + } finally { + if (writer != null) { + writer.close(); + } + } + } +} diff --git a/src/test/java/com/salesforce/dataloader/process/CsvEncodingProcessTest.java b/src/test/java/com/salesforce/dataloader/process/CsvEncodingProcessTest.java index a856a7a46..9049e47e7 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvEncodingProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvEncodingProcessTest.java @@ -29,8 +29,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; -import java.lang.reflect.Field; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collection; import java.util.Map; @@ -45,7 +44,11 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.dao.csv.CSVFileReader; +import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; +import com.salesforce.dataloader.model.TableRow; import com.sforce.async.CSVReader; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; @@ -62,7 +65,6 @@ @RunWith(Parameterized.class) public class CsvEncodingProcessTest extends ProcessTestBase { - private static final String UTF8 = "UTF-8"; private static final String JAPANESE = "Shift_JIS"; private static final String FILE_ENCODING_SYSTEM_PROPERTY = "file.encoding"; private final Map config; @@ -78,8 +80,8 @@ public CsvEncodingProcessTest(String fileEncoding, Map config) { @Parameterized.Parameters(name = "{0}, {1}") public static Collection getParameters() { return Arrays.asList( - TestVariant.builder().withSettings(TestSetting.BULK_API_ENABLED, TestSetting.WRITE_UTF8_ENABLED, TestSetting.READ_UTF8_ENABLED).withFileEncoding(UTF8).get(), - TestVariant.builder().withSettings(TestSetting.BULK_API_ENABLED, TestSetting.WRITE_UTF8_DISABLED, TestSetting.READ_UTF8_DISABLED).withFileEncoding(UTF8).get(), + TestVariant.builder().withSettings(TestSetting.BULK_API_ENABLED, TestSetting.WRITE_UTF8_ENABLED, TestSetting.READ_UTF8_ENABLED).withFileEncoding(StandardCharsets.UTF_8.name()).get(), + TestVariant.builder().withSettings(TestSetting.BULK_API_ENABLED, TestSetting.WRITE_UTF8_DISABLED, TestSetting.READ_UTF8_DISABLED).withFileEncoding(StandardCharsets.UTF_8.name()).get(), TestVariant.builder().withSettings(TestSetting.BULK_API_ENABLED, TestSetting.WRITE_UTF8_ENABLED, TestSetting.READ_UTF8_ENABLED).withFileEncoding(JAPANESE).get() //this one is suspect... how can we support the below condition (UTF8 language sent using disabled UTF8... this should blow up!) @@ -90,20 +92,12 @@ public static Collection getParameters() { @Before public void overrideSystemFileEncoding() throws Exception { originalFileEncoding = System.getProperty(FILE_ENCODING_SYSTEM_PROPERTY); - setSystemFileEncoding(fileEncoding); + System.setProperty(FILE_ENCODING_SYSTEM_PROPERTY, this.fileEncoding); } @After public void restoreOriginalSystemFileEncoding() throws Exception { - setSystemFileEncoding(originalFileEncoding); - } - - private void setSystemFileEncoding(String encoding) throws NoSuchFieldException, IllegalAccessException { - System.setProperty(FILE_ENCODING_SYSTEM_PROPERTY, encoding); - // following is necessary for new value to be properly read because default charset is cached - Field charset = Charset.class.getDeclaredField("defaultCharset"); - charset.setAccessible(true); - charset.set(null, null); + System.setProperty(FILE_ENCODING_SYSTEM_PROPERTY, this.originalFileEncoding); } @Test @@ -118,21 +112,30 @@ public void testUnicodeExtraction() throws Exception { private Map getBulkUnicodeExtractConfig(String soql) { final Map argMap = getTestConfig(OperationInfo.extract, true); - argMap.put(Config.ENTITY, "Account"); - argMap.put(Config.EXTRACT_SOQL, soql); - argMap.put(Config.ENABLE_EXTRACT_STATUS_OUTPUT, Config.TRUE); - argMap.put(Config.EXTRACT_REQUEST_SIZE, "2000"); + argMap.put(AppConfig.PROP_ENTITY, "Account"); + argMap.put(AppConfig.PROP_EXTRACT_SOQL, soql); + argMap.put(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT, AppConfig.TRUE); + argMap.put(AppConfig.PROP_EXPORT_BATCH_SIZE, "2000"); argMap.putAll(config); - argMap.remove(Config.MAPPING_FILE); + argMap.remove(AppConfig.PROP_MAPPING_FILE); return argMap; } private void validateExtraction(final String name, final Map testConfig) throws IOException { - FileInputStream fis = new FileInputStream(new File(testConfig.get(Config.DAO_NAME))); + FileInputStream fis = new FileInputStream(new File(testConfig.get(AppConfig.PROP_DAO_NAME))); try { - CSVReader rdr = new CSVReader(fis, "UTF-8"); - int nameidx = rdr.nextRecord().indexOf("NAME"); - assertEquals(name, rdr.nextRecord().get(nameidx)); + CSVFileReader rdr = new CSVFileReader(new File(testConfig.get(AppConfig.PROP_DAO_NAME)), + this.getController().getAppConfig(), false, true); + rdr.open(); + TableRow row = rdr.readTableRow(); + String extractedNameVal = (String)row.get("Name"); + assertEquals(name, extractedNameVal); + } catch (DataAccessObjectInitializationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (DataAccessObjectException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } finally { IOUtils.closeQuietly(fis); } diff --git a/src/test/java/com/salesforce/dataloader/process/CsvExtractAggregateQueryProcessTest.java b/src/test/java/com/salesforce/dataloader/process/CsvExtractAggregateQueryProcessTest.java index a94153d23..425afa17a 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvExtractAggregateQueryProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvExtractAggregateQueryProcessTest.java @@ -28,6 +28,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Map; import org.apache.commons.io.IOUtils; @@ -35,9 +36,12 @@ import org.junit.Test; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.exception.DataAccessObjectException; +import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.exception.ProcessInitializationException; +import com.salesforce.dataloader.model.TableRow; import com.sforce.async.CSVReader; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; @@ -58,9 +62,9 @@ public class CsvExtractAggregateQueryProcessTest extends ProcessTestBase { @Before public void setUpTestConfig() { testConfig = getTestConfig(OperationInfo.extract, true); - testConfig.put(Config.ENTITY, "Contact"); - testConfig.put(Config.ENABLE_EXTRACT_STATUS_OUTPUT, Config.TRUE); - testConfig.remove(Config.MAPPING_FILE); + testConfig.put(AppConfig.PROP_ENTITY, "Contact"); + testConfig.put(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT, AppConfig.TRUE); + testConfig.remove(AppConfig.PROP_MAPPING_FILE); } @Test @@ -68,20 +72,41 @@ public void testAggregateQuery() throws Exception { String accountId = insertAccount("acctNameXyz"); String contactId = insertContact(accountId); runExtraction("select Count(Id), Account.Name from Contact where Id='" + contactId + "' GROUP BY Account.Name"); - validateAccountNameInOutputFile("acctNameXyz"); + validateAccountNameInOutputFile("acctNameXyz", true); + runExtractionDoNotLimitOutputToQueryFields("select Count(Id), Account.Name from Contact where Id='" + contactId + "' GROUP BY Account.Name"); + validateAccountNameInOutputFile("acctNameXyz", false); } private void runExtraction(String extractionQuery) throws ProcessInitializationException, DataAccessObjectException { - testConfig.put(Config.EXTRACT_SOQL, extractionQuery); + testConfig.put(AppConfig.PROP_EXTRACT_SOQL, extractionQuery); + testConfig.put(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS, AppConfig.TRUE); runProcess(testConfig, 1, true); } - private void validateAccountNameInOutputFile(final String accountName) throws IOException { - FileInputStream fis = new FileInputStream(new File(testConfig.get(Config.DAO_NAME))); + private void runExtractionDoNotLimitOutputToQueryFields(String extractionQuery) throws ProcessInitializationException, DataAccessObjectException { + testConfig.put(AppConfig.PROP_EXTRACT_SOQL, extractionQuery); + testConfig.put(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS, AppConfig.FALSE); + runProcess(testConfig, 1, true); + } + + private void validateAccountNameInOutputFile(final String accountName, boolean isLimitOutputToQueryFields) throws IOException { + FileInputStream fis = new FileInputStream(new File(testConfig.get(AppConfig.PROP_DAO_NAME))); try { - CSVReader rdr = new CSVReader(fis, "UTF-8"); - int acctNameIndex = rdr.nextRecord().indexOf("ACCOUNT.NAME"); - assertEquals(accountName, rdr.nextRecord().get(acctNameIndex)); + CSVFileReader rdr = new CSVFileReader(new File(testConfig.get(AppConfig.PROP_DAO_NAME)), + this.getController().getAppConfig(), false, true); + rdr.open(); + TableRow row = rdr.readTableRow(); + String extractedNameVal = (String)row.get("Name"); + if (isLimitOutputToQueryFields) { + extractedNameVal = (String)row.get("Account.Name"); + } + assertEquals(accountName, extractedNameVal); + } catch (DataAccessObjectInitializationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (DataAccessObjectException e) { + // TODO Auto-generated catch block + e.printStackTrace(); } finally { IOUtils.closeQuietly(fis); } diff --git a/src/test/java/com/salesforce/dataloader/process/CsvExtractAllProcessTest.java b/src/test/java/com/salesforce/dataloader/process/CsvExtractAllProcessTest.java index 79aa08396..2cce89bc7 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvExtractAllProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvExtractAllProcessTest.java @@ -26,10 +26,16 @@ package com.salesforce.dataloader.process; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import com.salesforce.dataloader.TestSetting; +import com.salesforce.dataloader.TestVariant; + +import java.util.Arrays; +import java.util.Collection; import java.util.Map; /** @@ -38,9 +44,22 @@ * @author Aleksandr Shulman, Colin Jarvis * @since 21.0 */ +@SuppressWarnings("unused") @RunWith(Parameterized.class) public class CsvExtractAllProcessTest extends ProcessExtractTestBase { + @Parameterized.Parameters(name = "{0}") + public static Collection getParameters() { + return Arrays.asList( + // partner API + TestVariant.forSettings(TestSetting.BULK_API_DISABLED, TestSetting.BULK_V2_API_DISABLED), + // Bulk API + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_DISABLED), + // Bulk V2 Query API does not support query_all + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED) + ); + } + public CsvExtractAllProcessTest(Map config) throws Exception { super(config); } diff --git a/src/test/java/com/salesforce/dataloader/process/CsvExtractProcessTest.java b/src/test/java/com/salesforce/dataloader/process/CsvExtractProcessTest.java index 4b9adb15a..dca675a5c 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvExtractProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvExtractProcessTest.java @@ -26,19 +26,37 @@ package com.salesforce.dataloader.process; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.TestSetting; +import com.salesforce.dataloader.TestVariant; +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.ProcessInitializationException; +import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; +import com.sforce.soap.partner.GetUserInfoResult; +import com.sforce.soap.partner.SaveResult; +import com.sforce.soap.partner.sobject.SObject; +import com.sforce.ws.ConnectionException; + +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; import java.util.Map; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; /** @@ -48,8 +66,19 @@ * @since 21.0 */ @RunWith(Parameterized.class) +@SuppressWarnings("unused") public class CsvExtractProcessTest extends ProcessExtractTestBase { - + @Parameterized.Parameters(name = "{0}") + public static Collection getParameters() { + return Arrays.asList( + // partner API + TestVariant.forSettings(TestSetting.BULK_API_DISABLED, TestSetting.BULK_V2_API_DISABLED), + // Bulk API + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_DISABLED), + // Bulk V2 Query API + TestVariant.forSettings(TestSetting.BULK_V2_API_ENABLED)); + } + public CsvExtractProcessTest(Map config) { super(config); } @@ -81,6 +110,11 @@ public void testSoqlWithTableNameInSelect() throws Exception { public void testExtractAccountCsv() throws Exception { runTestExtractAccountCsv(); } + + @Test + public void testSelectFieldsSoql() throws Exception { + runTestSelectFieldsSoql(); + } @Override @Test @@ -98,21 +132,21 @@ public void testSoqlWithRelationships() throws Exception { } /** - * Test output of last run files. 1. Output is enabled, directory is not set (use default) 2. Output is enabled, - * directory is set 3. Output is disabled + * Test output of last run files. 1. Output is enabled, folder is not set (use default) 2. Output is enabled, + * folder is set 3. Output is disabled * * @hierarchy API.dataloader Csv Process Tests * @userstory Commenting existing data loader tests and uploading into QA force */ @Test public void testLastRunOutput() throws Exception { - // 1. Output is enabled (use default), directory is not set (use + // 1. Output is enabled (use default), folder is not set (use // default) String baseName = this.baseName; upsertSfdcAccounts(1); testLastRunOutput(true, baseName + "_default", true, null); - // 2. Output is enabled, directory is set + // 2. Output is enabled, folder is set testLastRunOutput(false, baseName + "_dirSet", true, System.getProperty("java.io.tmpdir")); // 3. Output is disabled @@ -130,31 +164,111 @@ public void testLastRunOutput() throws Exception { public void testForNonQueryableSObjects() throws Exception { runTestForNonQueryableSObjects(); } + + @Test + public void testPolymorphicRelationshipExtract() throws Exception { + // create a test lead + final String uid = getBinding().getUserInfo().getUserId(); + final String[] leadidArr = createLead(uid); + try { + final String soql = "SELECT Id, Owner.Name, Lead.Owner.Id, x.owner.lastname, OwnerId FROM Lead x where id='" + + leadidArr[0] + "'"; + final Map argmap = getExtractionTestConfig(soql, "Lead", true); + // run the extract + runProcess(argmap, 1); + GetUserInfoResult userInfo = getBinding().getUserInfo(); + + // open the results of the extraction + final CSVFileReader rdr = new CSVFileReader(new File(argmap.get(AppConfig.PROP_DAO_NAME)), getController().getAppConfig(), true, false); + rdr.open(); + TableRow row = rdr.readTableRow(); + assertNotNull(row); + assertEquals(5,row.getNonEmptyCellsCount()); + // validate the extract results are correct. + assertEquals(leadidArr[0], row.get("LID")); + assertTrue(userInfo.getUserFullName().contains(row.get("LNAME").toString())); + assertEquals(userInfo.getUserFullName(), row.get("NAME__RESULT")); + assertEquals(uid, row.get("OID")); + assertEquals(uid,row.get("OWNID")); + // validate that we have read the only result. there should be only one. + assertNull(rdr.readTableRow()); + } finally { + // cleanup here since the parent doesn't clean up leads + getBinding().delete(leadidArr); + } + + } + + /** creates a lead owned by the provided user */ + private String[] createLead(final String uid) throws ConnectionException { + final SObject lead = new SObject(); + // Create a lead sobject + lead.setType("Lead"); + lead.setField("LastName", "test lead"); + lead.setField("Company", "salesforce"); + lead.setField("OwnerId", uid); + + // insert the lead + final SaveResult[] result = getBinding().create(new SObject[] { lead }); + + // validate save result + assertNotNull(result); + assertEquals(1, result.length); + assertTrue(Arrays.toString(result[0].getErrors()), result[0].isSuccess()); + // get new lead id + final String[] leadidArr = new String[] { result[0].getId() }; + return leadidArr; + } + + /** + * Tests the extract operation on Account. Verifies that an extract operation with a soql query is performed + * correctly. + */ + @Test + public void testExtractSObjectWithJSONFieldType() throws Exception { + try { + // Test for regression in the fix for bug id: W-8551311 + // describeSObject for ApiEvent sObject fails if JSON FieldType enum + // does not exist in WSC because 'Records' field is of type JSON. + + final String soql = "SELECT Id FROM ApiEvent"; + final Map argmap = getExtractionTestConfig(soql, "ApiEvent", false); + // run the extract + runProcess(argmap, 0); + } finally { + // noop + } + } + /** * @param enableLastRunOutput */ private void testLastRunOutput(boolean useDefault, String baseProcessName, boolean enableOutput, String outputDir) throws DataAccessObjectException, ProcessInitializationException { final String soql = "Select ID FROM ACCOUNT WHERE " + ACCOUNT_WHERE_CLAUSE + " limit 1"; - Map argMap = getTestConfig(soql, "Account", false); - argMap.remove(Config.LAST_RUN_OUTPUT_DIR); + Map argMap = getExtractionTestConfig(soql, "Account", false); + argMap.remove(AppConfig.PROP_LAST_RUN_OUTPUT_DIR); // set last run output paramerers if (!useDefault) { - argMap.put(Config.ENABLE_LAST_RUN_OUTPUT, String.valueOf(enableOutput)); - argMap.put(Config.LAST_RUN_OUTPUT_DIR, outputDir); + argMap.put(AppConfig.PROP_ENABLE_LAST_RUN_OUTPUT, String.valueOf(enableOutput)); + argMap.put(AppConfig.PROP_LAST_RUN_OUTPUT_DIR, outputDir); } this.baseName = baseProcessName; Controller theController = runProcess(argMap, 1); - Config config = theController.getConfig(); - String lastRunFilePath = config.getLastRunFilename(); + AppConfig appConfig = theController.getAppConfig(); + String lastRunFilePath = appConfig.getLastRunFilename(); File lastRunFile = new File(lastRunFilePath); try { - String defaultFileName = baseProcessName + "_lastRun.properties"; - File expectedFile = useDefault ? new File(config.constructConfigFilePath(defaultFileName)) : new File( + String lastrunFileNamePrefix = appConfig.getString(AppConfig.PROP_PROCESS_NAME); + if (lastrunFileNamePrefix == null || lastrunFileNamePrefix.isBlank()) { + lastrunFileNamePrefix = appConfig.getString(AppConfig.PROP_ENTITY) + appConfig.getString(AppConfig.PROP_OPERATION); + } + String defaultFileName = lastrunFileNamePrefix + "_lastRun.properties"; + File expectedFile = useDefault ? new File(appConfig.constructConfigFilePath(defaultFileName)) : new File( outputDir, defaultFileName); if (enableOutput) { assertTrue("Could not find last run file: " + lastRunFilePath, lastRunFile.exists()); @@ -193,4 +307,53 @@ public void testMalformedQueries() throws Exception { runMalformedQueriesTest(); } + @Test + public void testBinaryDataInRTFQueryResult() throws Exception { + testBinaryDataInRTFQueryResult("true"); + testBinaryDataInRTFQueryResult("false"); + } + + private void testBinaryDataInRTFQueryResult(String inclueRTFBinaryData) throws Exception { + // insert an account with binary data + Map insertArgMap = getTestConfig(OperationInfo.insert, + getTestDataDir() + "/acctsWithBinaryDataInRTF.csv", false); + Controller controller = runProcess(insertArgMap, 1); + List ids = new ArrayList(); + String fileName = controller.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + final CSVFileReader successRdr = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); + String idFieldName = this.isBulkV2APIEnabled(insertArgMap)?"sf__Id":"ID"; + try { + for (TableRow row : successRdr.readTableRowList(Integer.MAX_VALUE)) { + final String rowId = (String) row.get(idFieldName); + if (rowId != null) { + ids.add(rowId); + } + } + } finally { + successRdr.close(); + } + + // set config property loader.query.includeBinaryData to true + String soql = "Select ID,RICHTEXT__C FROM Account where id='" + ids.get(0) + "'"; + Map queryArgMap = getExtractionTestConfig(soql, "Account", false); + queryArgMap.put(AppConfig.PROP_INCLUDE_RICH_TEXT_FIELD_DATA_IN_QUERY_RESULTS, inclueRTFBinaryData); + + // query the account and verify results + controller = runProcess(queryArgMap, 1); + CSVFileReader queryResultsReader = new CSVFileReader(new File(queryArgMap.get(AppConfig.PROP_DAO_NAME)), getController().getAppConfig(), true, false); + queryResultsReader.open(); + TableRow queryResultsRow = queryResultsReader.readTableRow(); + String queryResultsRTVal = (String)queryResultsRow.get("RICHTEXT__c"); + + if ("true".equalsIgnoreCase(inclueRTFBinaryData)) { + CSVFileReader uploadedCSVReader = new CSVFileReader(new File(getTestDataDir() + "/acctsWithBinaryDataInRTF.csv"), getController().getAppConfig(), true, false); + uploadedCSVReader.open(); + TableRow uploadedRow = uploadedCSVReader.readTableRow(); + String uploadedRTVal = (String)queryResultsRow.get("RICHTEXT__c"); + assertEquals("Binary data in query result file does not match uploaded data: " + + queryArgMap.get(AppConfig.PROP_DAO_NAME), queryResultsRTVal, uploadedRTVal); + } else { + assertTrue(queryResultsRTVal.contains(".file.force.com/servlet/rtaImage?")); + } + } } diff --git a/src/test/java/com/salesforce/dataloader/process/CsvHardDeleteTest.java b/src/test/java/com/salesforce/dataloader/process/CsvHardDeleteTest.java index b88a7ae70..0ad70a008 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvHardDeleteTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvHardDeleteTest.java @@ -28,12 +28,15 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; + import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -54,6 +57,7 @@ * @userstory Commenting existing data loader tests and uploading into QA force */ @RunWith(Parameterized.class) +@SuppressWarnings("unused") public class CsvHardDeleteTest extends ProcessTestBase { public CsvHardDeleteTest(Map config) { @@ -64,8 +68,11 @@ public CsvHardDeleteTest(Map config) { public static Collection getTestParameters() { return Arrays.asList( TestVariant.forSettings(TestSetting.BULK_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED), TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED), + TestVariant.forSettings(TestSetting.BULK_V2_API_ENABLED) + ); } /** @@ -94,7 +101,7 @@ public void testHardDeleteUserPermOff() throws Exception { // attempt to hard delete 100 accounts as a user without the "Bulk API Hard Delete" user perm enabled final Map argMap = getHardDeleteTestConfig(new AccountIdTemplateListener(100)); // change the configured user to be the standard user (ie without the perm) - argMap.put(Config.USERNAME, getProperty("test.user.restricted")); + argMap.put(AppConfig.PROP_USERNAME, getProperty("test.user.restricted")); runProcessNegative(argMap, "You need the Bulk API Hard Delete user permission to permanently delete records."); } @@ -137,7 +144,7 @@ public void testHardDeleteEmptyCsvFile() throws Exception { } /** - * Hard Delete - Negative test. Uncheck Bull Api setting in data loader to verify that Hard Delete operation cannot + * Hard Delete - Negative test. Uncheck Bulk Api setting in data loader to verify that Hard Delete operation cannot * be done. */ @Test @@ -147,13 +154,14 @@ public void testHardDeleteBulkApiSetToFalse() throws DataAccessObjectException { // set batch process parameters Map argMap = getHardDeleteTestConfig(new AccountIdTemplateListener(1)); - argMap.remove(Config.BULK_API_ENABLED); + argMap.remove(AppConfig.PROP_BULK_API_ENABLED); + argMap.remove(AppConfig.PROP_BULKV2_API_ENABLED); try { runProcess(argMap, 889); Assert.fail("hard delete should not succeed if bulk api is turned off"); } catch (Exception e) { final String msg = e.getMessage(); - final String expected = "java.lang.UnsupportedOperationException: Error instantiating operation hard_delete: could not instantiate class: null."; + final String expected = "Error instantiating operation hard_delete: could not instantiate class: null."; assertEquals("Wrong exception thrown when attempting to do hard delete with bulk api off : ", expected, msg); } @@ -166,7 +174,7 @@ public InvalidIdTemplateListener(int numValidAccounts) { } @Override - public void updateRow(int idx, Row row) { + public void updateRow(int idx, TableRow row) { if (idx == 0) row.put("ID", "abcde0123456789XYZ"); else @@ -215,7 +223,7 @@ public void testHardDeleteInvalidIDFailsOtherValidIDPasses() throws Exception { public void testHardDeleteIDFromOtherObjectFails() throws Exception { // set batch process parameters Map argMap = getHardDeleteTestConfig(new AccountIdTemplateListener(1)); - argMap.put(Config.ENTITY, "Contact"); + argMap.put(AppConfig.PROP_ENTITY, "Contact"); Controller theController = runProcessWithErrors(argMap, 0, 1); // verify there were errors during operation @@ -231,7 +239,7 @@ public HeterogeneousIdTemplateListener(int numAccounts, int numContacts) { } @Override - public void updateRow(int idx, Row row) { + public void updateRow(int idx, TableRow row) { if (idx < this.contactIds.length) row.put("ID", this.contactIds[idx]); else diff --git a/src/test/java/com/salesforce/dataloader/process/CsvProcessAttachmentTest.java b/src/test/java/com/salesforce/dataloader/process/CsvProcessAttachmentTest.java index dd5700761..7db773d7e 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvProcessAttachmentTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvProcessAttachmentTest.java @@ -26,26 +26,30 @@ package com.salesforce.dataloader.process; -import com.salesforce.dataloader.TestProgressMontitor; import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.ProcessInitializationException; import com.salesforce.dataloader.exception.UnsupportedOperationException; import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import com.sforce.soap.partner.QueryResult; +import com.sforce.soap.partner.SaveResult; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; import org.junit.Assert; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import java.io.File; import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -74,7 +78,11 @@ public static Collection getTestParameters() { TestVariant.defaultSettings(), TestVariant.forSettings(TestSetting.BULK_API_ENABLED), TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED) + ); } @Test @@ -84,18 +92,56 @@ public void testCreateAttachment() throws ProcessInitializationException, DataAc new AttachmentTemplateListener()); final Map argMap = getTestConfig(OperationInfo.insert, fileName, false); - argMap.put(Config.ENTITY, "Attachment"); + argMap.put(AppConfig.PROP_ENTITY, "Attachment"); // this feature does not work when bulk api is enabled but the zip content type is not final boolean bulkApi = isBulkAPIEnabled(argMap); - final boolean zipContent = isSettingEnabled(argMap, Config.BULK_API_ZIP_CONTENT); - if (bulkApi && !zipContent) { + final boolean bulkV2Api = isBulkV2APIEnabled(argMap); + final boolean zipContent = isSettingEnabled(argMap, AppConfig.PROP_BULK_API_ZIP_CONTENT); + if ((bulkApi || bulkV2Api) && !zipContent) { final String failureMessage = "Data Loader cannot map \"Body\" field using Bulk API and CSV content type. Please enable the ZIP_CSV content type for Bulk API."; runProcessNegative(argMap, failureMessage); + } else if (bulkV2Api && zipContent) { + final String failureMessage = "Exit UNSUPPORTEDCONTENTTYPE : UnsupportedContentType : ZIP_CSV is not a valid Content-Type"; + runProcessNegative(argMap, failureMessage); } else { runProcess(argMap, 1); } } + + @Test + public void testPolymorphicRelationshipInAttachment() throws ProcessInitializationException, DataAccessObjectException, ConnectionException { + // convert the template using the parent account id + Map configMap = getTestConfig(OperationInfo.insert, false); + + // this feature does not work when bulk api is enabled but the zip content type is not + final boolean bulkApi = isBulkAPIEnabled(configMap); + final boolean bulkV2Api = isBulkV2APIEnabled(configMap); + final boolean zipContent = isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT); + if (bulkApi && !zipContent) { + // attachment is supported only if content is zipped + return; + } else if (bulkV2Api) { + // bulk v2 does not support zip content and therefore attachment + return; + } + AccountGenerator acctGen = new AccountGenerator(); + SObject[] parentAccts = new SObject[1]; + parentAccts[0] = acctGen.getObject(0, false); + // value of Oracle_id__c = 1-000000 + SaveResult[] results = getBinding().create(parentAccts); + parentAccts[0].addField("id", results[0]); + + ContactGenerator contactGen = new ContactGenerator(); + SObject[] parentContacts = new SObject[1]; + parentContacts[0] = contactGen.getObject(0, false); + // value of Oracle_id__c = 1-000000 + results = getBinding().create(parentContacts); + parentContacts[0].addField("id", results[0]); + + configMap.put(AppConfig.PROP_ENTITY, "Attachment"); + runProcess(configMap, 2); + } /** * Verify that multiple binary files can be correctly zipped up and inserted into a record. @@ -111,14 +157,20 @@ public void testCreateAttachmentMultipleFiles() throws Exception { myAttachmentTemplateListener); final Map argMap = getTestConfig(OperationInfo.insert, fileName, false); - argMap.put(Config.ENTITY, "Attachment"); + argMap.put(AppConfig.PROP_ENTITY, "Attachment"); + // force multiple batches + argMap.put(AppConfig.PROP_IMPORT_BATCH_SIZE, "1"); // this feature does not work when bulk api is enabled but the zip content type is not final boolean bulkApi = isBulkAPIEnabled(argMap); - final boolean zipContent = isSettingEnabled(argMap, Config.BULK_API_ZIP_CONTENT); - if (bulkApi && !zipContent) { + final boolean bulkV2Api = isBulkV2APIEnabled(argMap); + final boolean zipContent = isSettingEnabled(argMap, AppConfig.PROP_BULK_API_ZIP_CONTENT); + if ((bulkApi || bulkV2Api) && !zipContent) { final String failureMessage = "Data Loader cannot map \"Body\" field using Bulk API and CSV content type. Please enable the ZIP_CSV content type for Bulk API."; runProcessNegative(argMap, failureMessage); + } else if (bulkV2Api && zipContent) { + final String failureMessage = "Exit UNSUPPORTEDCONTENTTYPE : UnsupportedContentType : ZIP_CSV is not a valid Content-Type"; + runProcessNegative(argMap, failureMessage); } else { runProcessWithAttachmentListener(argMap, 3, myAttachmentTemplateListener, "Bay-Bridge.jpg", "BayBridgeBW.jpg", "BayBridgeFromTreasureIsland.jpg"); } @@ -130,7 +182,7 @@ public AttachmentTemplateListener() { } @Override - public void updateRow(int idx, Row row) { + public void updateRow(int idx, TableRow row) { // set parent account id row.put("ParentId", getAccountIds()[0]); // make body pathname absolute @@ -144,21 +196,21 @@ private Controller runProcessWithAttachmentListener(Map argMap, AttachmentTemplateListener myAttachmentTemplateListener, String... files) throws ProcessInitializationException, DataAccessObjectException, ConnectionException, IOException { - if (argMap == null) argMap = getTestConfig(); - - final ProcessRunner runner = ProcessRunner.getInstance(argMap); - runner.setName(this.baseName); - - final TestProgressMontitor monitor = new TestProgressMontitor(); - runner.run(monitor); + final IProcess runner = this.runBatchProcess(argMap); + ILoaderProgress monitor = runner.getMonitor(); Controller controller = runner.getController(); // verify process completed as expected if (expectProcessSuccess) { - verifyInsertCorrectByContent(controller, createAttachmentFileMap(files), myAttachmentTemplateListener); // this should also still work assertTrue("Process failed: " + monitor.getMessage(), monitor.isSuccess()); + Map attachments = controller.getLastExecutedAction().getVisitor().getAttachments(); + if ( attachments!= null) { + // attachments map must be cleared when a batch is uploaded + assertTrue("Incorrect number of attachments in the batch: expected 0, actual " + attachments.keySet().size(), + attachments.keySet().size() == 0); + } verifyFailureFile(controller, numFailures); // A.S.: To be removed and replaced verifySuccessFile(controller, numInserts, numUpdates, false); @@ -186,7 +238,7 @@ private Controller runProcessWithErrorsWithAttachmentListener(Map config) { @@ -71,9 +79,14 @@ public CsvProcessTest(Map config) { public static Collection getParameters() { return Arrays.asList( TestVariant.defaultSettings(), + TestVariant.forSettings(TestSetting.COMPOSITE_REST_API_ENABLED), TestVariant.forSettings(TestSetting.BULK_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.COMPRESSION_DISABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED), TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED) + ); } /** @@ -81,24 +94,120 @@ public static Collection getParameters() { */ @Test public void testInsertAccountCsv() throws Exception { - runProcess(getTestConfig(OperationInfo.insert, false), 100); + Map configMap = getTestConfig(OperationInfo.insert, false); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } + runProcess(configMap, 100); + } + + @Test + public void testInsertAccountWithMultipleBatchesCSV() throws ProcessInitializationException, DataAccessObjectException { + Map configMap = getTestConfig(OperationInfo.insert, false); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } + configMap.put(AppConfig.PROP_IMPORT_BATCH_SIZE, "1"); + Controller controller = runProcessWithErrors(configMap, 2, 1); + String successFileName = controller.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + File successFile = new File(successFileName); + CSVFileReader csvReader = new CSVFileReader(successFile, controller.getAppConfig(), false, false); + TableRow row1 = csvReader.readTableRow(); + TableRow row2 = csvReader.readTableRow(); + String oracleIdRow1 = (String)row1.get("oracle_id"); + String oracleIdRow2 = (String)row2.get("oracle_id"); + if (oracleIdRow1 == null) { + oracleIdRow1 = (String)row1.get("oracle_id__c"); + oracleIdRow2 = (String)row2.get("oracle_id__c"); + } + assertTrue("incorrect success row 1", oracleIdRow1.equals("o1")); + assertTrue("incorrect success row 2, expected oracle_id = o3", oracleIdRow2.equals("o3")); } + /** + * Tests the insert operation on Account - Positive test. + */ + @Test + public void testInsertTaskWithContactAsWhoCsv() throws Exception { + Map configMap = getTestConfig(OperationInfo.insert, false); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } + Map sforceMapping = new HashMap(); + sforceMapping.put("Email", "contactFor@PolymorphicMappingOfTask.com"); + sforceMapping.put("Subject", "Contact to test Polymorphic mapping of Who relationship on Task"); + sforceMapping.put("FirstName", "newFirstName" + System.currentTimeMillis()); + sforceMapping.put("LastName", "newLastName" + System.currentTimeMillis()); + // Title is set for easier test data cleanup + sforceMapping.put("Title", CONTACT_TITLE_PREFIX + System.currentTimeMillis()); + + String extIdField = setExtIdField(DEFAULT_CONTACT_EXT_ID_FIELD); + Object extIdValue = getRandomExtId("Contact", CONTACT_WHERE_CLAUSE, null); + sforceMapping.put(extIdField, extIdValue); + + String oldExtIdField = getController().getAppConfig().getString(AppConfig.PROP_IDLOOKUP_FIELD); + setExtIdField(extIdField); + doUpsert("Contact", sforceMapping); + setExtIdField(oldExtIdField); + configMap.put(AppConfig.PROP_ENTITY, "Task"); + runProcess(configMap, 1); + } + /** * Tests update operation with input coming from a CSV file. Relies on the id's in the CSV on being in the database */ @Test public void testUpdateAccountCsv() throws Exception { - runProcess(getUpdateTestConfig(false, null, 100), 100); + Map configMap = getUpdateTestConfig(false, null, 100); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + || isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + ) { + return; + } + runProcess(configMap, 100); } - + + @Test + public void testUpdateAccountWithExternalIdCsv() throws Exception { + Map configMap = getUpdateTestConfig(false, null, 5); + if (!isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } + runProcess(configMap, 5); + } + /** * Upsert the records from CSV file */ @Test public void testUpsertAccountCsv() throws Exception { + Map configMap = getUpdateTestConfig(true, DEFAULT_ACCOUNT_EXT_ID_FIELD, 50); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } // manually inserts 50 accounts, then upserts 100 accounts (50 inserts and 50 updates) - runUpsertProcess(getUpdateTestConfig(true, DEFAULT_ACCOUNT_EXT_ID_FIELD, 50), 50, 50); + runUpsertProcess(configMap, 50, 50); } /** @@ -114,7 +223,14 @@ public void testUpsertAccountCsv() throws Exception { */ @Test public void testConstantMappingInCsv() throws Exception { - + Map configMap = getTestConfig(OperationInfo.insert, false); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } // The use case is as follows: // This company in this scenario only does business in the state of CA, therefore billing and shipping // addresses are hard coded to that. @@ -124,8 +240,7 @@ public void testConstantMappingInCsv() throws Exception { final String industryValue = "Aerospace"; // insert the values - Map argumentMap = getTestConfig(OperationInfo.insert, false); - for (SObject acct : retrieveAccounts(runProcess(argumentMap, 2), "Industry", "BillingState", "ShippingState")) { + for (SObject acct : retrieveAccounts(runProcess(configMap, 2), "Industry", "BillingState", "ShippingState")) { assertEquals("Incorrect value for industry returned", industryValue, acct.getField("Industry")); @@ -146,6 +261,15 @@ public void testConstantMappingInCsv() throws Exception { */ @Test public void testDescriptionAsConstantMappingInCsv() throws Exception { + Map configMap = getTestConfig(OperationInfo.insert, getTestDataDir() + + "/constantMappingInCsv.csv", false); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } // The use case is as follows: // This company in this scenario only does business in the state of CA, therefore billing and shipping // addresses are hard coded to that. @@ -154,9 +278,8 @@ public void testDescriptionAsConstantMappingInCsv() throws Exception { final String descriptionValue = "Some Description"; // insert the values - Map argumentMap = getTestConfig(OperationInfo.insert, getTestDataDir() - + "/constantMappingInCsv.csv", false); - Controller controller = runProcess(argumentMap, 2); + + Controller controller = runProcess(configMap, 2); for (SObject acct : retrieveAccounts(controller, "Description", "BillingState", "ShippingState")) { assertEquals("Incorrect value for billing state returned", stateValue, acct.getField("BillingState")); assertEquals("Incorrect value for shipping state returned", stateValue, acct.getField("ShippingState")); @@ -173,7 +296,15 @@ public void testDescriptionAsConstantMappingInCsv() throws Exception { */ @Test public void testFieldAndConstantFieldClash() throws Exception { - + Map configMap = getTestConfig(OperationInfo.insert, + getTestDataDir() + "/constantMappingInCsvClashing.csv", false); + if (isSettingEnabled(configMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(configMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(configMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(configMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } // The use case is as follows: // This company in this scenario only does business in the state of CA, therefore billing and shipping // addresses are hard coded to that. @@ -183,9 +314,7 @@ public void testFieldAndConstantFieldClash() throws Exception { final String industryValue = "Aerospace"; // insert the values - Map argumentMap = getTestConfig(OperationInfo.insert, - getTestDataDir() + "/constantMappingInCsvClashing.csv", false); - for (SObject acct : retrieveAccounts(runProcess(argumentMap, 2), "Industry", "BillingState", "ShippingState")) { + for (SObject acct : retrieveAccounts(runProcess(configMap, 2), "Industry", "BillingState", "ShippingState")) { assertEquals("Incorrect value for industry returned", industryValue, acct.getField("Industry")); @@ -235,11 +364,11 @@ public void testNullConstantAssignment() throws Exception { private SObject[] retrieveAccounts(Controller resultController, String... accountFieldsToReturn) throws Exception { List ids = new ArrayList(); - String fileName = resultController.getConfig().getString(Config.OUTPUT_SUCCESS); - final CSVFileReader successRdr = new CSVFileReader(fileName, getController()); + String fileName = resultController.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + final CSVFileReader successRdr = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); try { // TODO: revise the use of Integer.MAX_VALUE - for (Row row : successRdr.readRowList(Integer.MAX_VALUE)) { + for (TableRow row : successRdr.readTableRowList(Integer.MAX_VALUE)) { final String rowId = (String) row.get("ID"); if (rowId != null) { ids.add(rowId); @@ -253,7 +382,7 @@ private SObject[] retrieveAccounts(Controller resultController, String... accoun // query them and verify that they have the values StringBuilder fields = new StringBuilder("id"); for(String field : accountFieldsToReturn) { - fields.append(",").append(field); + fields.append(AppUtil.COMMA).append(field); } SObject[] sobjects = getBinding().retrieve(fields.toString(), "Account", ids.toArray(new String[ids.size()])); @@ -262,24 +391,54 @@ private SObject[] retrieveAccounts(Controller resultController, String... accoun } /** - * Tests Upsert on foreign key for the records based on the CSV file + * Tests Upsert on non-polymorphic foreign key (Relationship lookup) for the records + * using idlookup field of the parent object */ @Test - public void testUpsertFkAccountCsv() throws Exception { + public void testUpsertFkAccountOldFormatCsv() throws Exception { + Map configMap = getTestConfig(OperationInfo.upsert, false); + if (isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } // manually inserts 100 accounts, then upserts specifying account parent for 50 accounts runUpsertProcess(getUpdateTestConfig(true, DEFAULT_ACCOUNT_EXT_ID_FIELD, 100), 0, 50); } + + @Test + public void testUpsertFkAccountNewFormatCsv() throws Exception { + Map configMap = getTestConfig(OperationInfo.upsert, false); + if (isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } + // manually inserts 100 accounts, then upserts specifying account parent for 5 accounts + runUpsertProcess(getUpdateTestConfig(true, DEFAULT_ACCOUNT_EXT_ID_FIELD, 10), 0, 5); + } /** * Tests that Deleting the records based on a CSV file works */ @Test public void testDeleteAccountCsv() throws Exception { + Map configMap = getTestConfig(OperationInfo.delete, false); + if (isSettingEnabled(configMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } AccountIdTemplateListener listener = new AccountIdTemplateListener(100); String deleteFileName = convertTemplateToInput(baseName + "Template.csv", baseName + ".csv", listener); Map argMap = getTestConfig(OperationInfo.delete, deleteFileName, false); Controller theController = runProcess(argMap, 100); - verifySuccessIds(theController, listener.getAccountIds()); + String[] accountIds = listener.getAccountIds(); + verifySuccessIds(theController, accountIds); + if (argMap.containsKey(AppConfig.PROP_BULK_API_ENABLED) && argMap.get(AppConfig.PROP_BULK_API_ENABLED).equalsIgnoreCase("true")) { + return; + } + // partner API - do an undelete operation + argMap.put(AppConfig.PROP_OPERATION, OperationInfo.undelete.name()); + theController = runProcess(argMap, 100); + verifySuccessIds(theController, accountIds); + argMap.put(AppConfig.PROP_OPERATION, OperationInfo.delete.name()); + theController = runProcess(argMap, 100); + verifySuccessIds(theController, accountIds); } private class AttachmentTemplateListener extends AccountIdTemplateListener { @@ -288,7 +447,7 @@ public AttachmentTemplateListener() { } @Override - public void updateRow(int idx, Row row) { + public void updateRow(int idx, TableRow row) { // set parent account id row.put("ParentId", getAccountIds()[0]); // make body pathname absolute @@ -304,14 +463,24 @@ public void testCreateAttachment() throws Exception { new AttachmentTemplateListener()); final Map argMap = getTestConfig(OperationInfo.insert, fileName, false); - argMap.put(Config.ENTITY, "Attachment"); - + argMap.put(AppConfig.PROP_ENTITY, "Attachment"); + if (isSettingEnabled(argMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } + if (isSettingEnabled(argMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO)) { + return; + } + // this feature does not work when bulk api is enabled but the zip content type is not final boolean bulkApi = isBulkAPIEnabled(argMap); - final boolean zipContent = isSettingEnabled(argMap, Config.BULK_API_ZIP_CONTENT); - if (bulkApi && !zipContent) { + final boolean bulkV2Api = isBulkV2APIEnabled(argMap); + final boolean zipContent = isSettingEnabled(argMap, AppConfig.PROP_BULK_API_ZIP_CONTENT); + if ((bulkApi || bulkV2Api) && !zipContent) { final String failureMessage = "Data Loader cannot map \"Body\" field using Bulk API and CSV content type. Please enable the ZIP_CSV content type for Bulk API."; runProcessNegative(argMap, failureMessage); + } else if (bulkV2Api && zipContent) { + final String failureMessage = "Exit UNSUPPORTEDCONTENTTYPE : UnsupportedContentType : ZIP_CSV is not a valid Content-Type"; + runProcessNegative(argMap, failureMessage); } else { runProcess(argMap, 1); } @@ -332,7 +501,9 @@ public void testNonMappedFieldsPermittedInDLTransaction() throws Exception { // insert the values Map argumentMap = getTestConfig(OperationInfo.insert, getTestDataDir() + "/accountsForInsert.csv", false); - + if (isSettingEnabled(argumentMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } SObject[] returnedAccounts = retrieveAccounts(runProcess(argumentMap, numberOfRows), "ShippingState", "Industry"); @@ -346,6 +517,104 @@ public void testNonMappedFieldsPermittedInDLTransaction() throws Exception { } } + /** + * Verify that if not all columns are matched, that the DL operation cannot go forward. + * + * @expectedResults Assert that all the records were inserted and that the constant value was mapped as well. + * + */ + @Test + public void testHtmlFormattingInInsert() throws Exception { + _doTestHtmlFormattingInInsert(true); + _doTestHtmlFormattingInInsert(false); + } + + private void _doTestHtmlFormattingInInsert(boolean preserveWhitespaceInRichText) throws Exception { + final int NONBREAKING_SPACE_ASCII_VAL = 0xA0; + final int numberOfRows = 4; + + // insert the values + Map argumentMap = getTestConfig(OperationInfo.insert, + getTestDataDir() + "/accountsForInsert.csv", + getTestDataDir() + "/nonMappedFieldsPermittedInDLTransactionMap.sdl", + false); + if (isSettingEnabled(argumentMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } + if (isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(argumentMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(argumentMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } + argumentMap.put(AppConfig.PROP_LOAD_PRESERVE_WHITESPACE_IN_RICH_TEXT, + Boolean.toString(preserveWhitespaceInRichText)); + + + SObject[] returnedAccounts = retrieveAccounts(runProcess(argumentMap, + numberOfRows), "RichText__c", "Name"); + + for (SObject acct : returnedAccounts) { + String companyName = (String)acct.getField("Name"); + String textWithSpaceChars = (String)acct.getField("RichText__c"); + textWithSpaceChars = textWithSpaceChars.replace((char)NONBREAKING_SPACE_ASCII_VAL, ' '); + if (companyName.equalsIgnoreCase("Company A")) { + boolean isHTMLFormattingPreserved = textWithSpaceChars.contains("

    "); + assertEquals("HTML formatting not preserved for company " + companyName, + true, isHTMLFormattingPreserved); + isHTMLFormattingPreserved = textWithSpaceChars.contains(""); + assertEquals("HTML formatting not preserved for company " + companyName, + true, isHTMLFormattingPreserved); + String spaces = textWithSpaceChars.substring(3,7); + for (int i = 0; i < spaces.length(); i++) { + char c = spaces.charAt(i); + int cval = c; + boolean isSpaceChar = false; + if (cval == NONBREAKING_SPACE_ASCII_VAL || cval == ' ') { + isSpaceChar = true; + } + assertEquals("spaces not preserved for company " + companyName, true, isSpaceChar); + } + continue; + } else if (companyName.equalsIgnoreCase("Company C")) { + int idx = textWithSpaceChars.indexOf('<'); + if (idx == -1) { + idx = textWithSpaceChars.indexOf("<"); + } + if (idx != -1) { + idx += 4; + if (textWithSpaceChars.charAt(idx+1) != ' ' + || textWithSpaceChars.charAt(idx+2) != ' ' + || textWithSpaceChars.charAt(idx+1) != ' ') { + assertEquals("spaces after < character not preserved for company " + companyName, true, false); + } + } + } + String textWithoutLeadingSpaceChars = textWithSpaceChars.stripLeading(); + String textWithoutTrailingSpaceChars = textWithSpaceChars.stripTrailing(); + int numLeadingChars = textWithSpaceChars.length() - textWithoutLeadingSpaceChars.length(); + int numTrailingChars = textWithSpaceChars.length() - textWithoutTrailingSpaceChars.length(); + if (preserveWhitespaceInRichText) { + assertEquals("Incorrect value for RichText returned for " + companyName, + 4, numLeadingChars); + assertEquals("Incorrect value for RichText returned for " + companyName, + 2, numTrailingChars); + } else { + assertEquals("Incorrect value for RichText returned for " + companyName, + 0, numLeadingChars); + assertEquals("Incorrect value for RichText returned for " + companyName, + 0, numTrailingChars); + + } + } + } + + /** * * Verify that Date/Time with time zone, when truncated to just date, gets transferred and interpreted correctly. @@ -362,11 +631,18 @@ public void testTimezoneNotTruncated() throws Exception { TimeZone TZ = TimeZone.getTimeZone("GMT"); - DateConverter converter = new DateConverter(TZ, false); + DateTimeConverter converter = new DateTimeConverter(TZ, false); //find the csv file Map argumentMap = getTestConfig(OperationInfo.insert, getTestDataDir() + "/timeZoneFormatTesting.csv", false); - + if (isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(argumentMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + || isSettingEnabled(argumentMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(argumentMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } //insert into the account on the custom fields specified SObject[] returnedAccounts = retrieveAccounts(runProcess(argumentMap, numberOfRows), dateField); @@ -390,7 +666,7 @@ public void testTimezoneNotTruncated() throws Exception { @Test public void testErrorsGeneratedOnInvalidDateMatching() throws Exception { - runTestErrorsGeneratedOnInvalidDateMatchWithOffset(0, 3,3); + runTestErrorsGeneratedOnInvalidDateMatchWithOffset(0, 4, 2); } /** @@ -402,7 +678,81 @@ public void testErrorsGeneratedOnInvalidDateMatching() throws Exception { */ @Test public void testErrorsGeneratedOnInvalidDateMatchingWithOffset() throws Exception { - runTestErrorsGeneratedOnInvalidDateMatchWithOffset(2, 2, 2); + runTestErrorsGeneratedOnInvalidDateMatchWithOffset(2, 3, 1); + } + + @Test + public void testOneToManySforceFieldsMappingInCsv() throws Exception { + // The use case is as follows: + // This company in this scenario only does business in the state of CA, therefore billing and shipping + // addresses are hard coded to that. + // Also, all of its descriptions are constant. + + // insert the values + Map argumentMap = getTestConfig(OperationInfo.insert, getTestDataDir() + + "/oneToManySforceFieldsMappingInCsv.csv", false); + if (isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(argumentMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + || isSettingEnabled(argumentMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(argumentMap, AppConfig.PROP_NO_COMPRESSION) + ) { + return; + } + Controller controller = runProcess(argumentMap, 2); + for (SObject acct : retrieveAccounts(controller, "Name", "Description", "BillingState", "ShippingState")) { + if ("ABC Corp".equals(acct.getField("Name"))) { + final String stateValue = "California"; + assertEquals("Incorrect value for billing state returned", stateValue, acct.getField("BillingState")); + assertEquals("Incorrect value for shipping state returned", stateValue, acct.getField("ShippingState")); + assertEquals("Incorrect value for description returned", stateValue, acct.getField("Description")); + } else if ("XYZ Corp".equals(acct.getField("Name"))) { + final String stateValue = "New York"; + assertEquals("Incorrect value for billing state returned", stateValue, acct.getField("BillingState")); + assertEquals("Incorrect value for shipping state returned", stateValue, acct.getField("ShippingState")); + assertEquals("Incorrect value for description returned", stateValue, acct.getField("Description")); + } + } + + // test with insert nulls set to true. + argumentMap.put(AppConfig.PROP_INSERT_NULLS, "true"); + controller = runProcess(argumentMap, 2); + for (SObject acct : retrieveAccounts(controller, "Name", "Description", "BillingState", "ShippingState")) { + if ("ABC Corp".equals(acct.getField("Name"))) { + final String stateValue = "California"; + assertEquals("Incorrect value for billing state returned", stateValue, acct.getField("BillingState")); + assertEquals("Incorrect value for shipping state returned", stateValue, acct.getField("ShippingState")); + assertEquals("Incorrect value for description returned", stateValue, acct.getField("Description")); + } else if ("XYZ Corp".equals(acct.getField("Name"))) { + final String stateValue = "New York"; + assertEquals("Incorrect value for billing state returned", stateValue, acct.getField("BillingState")); + assertEquals("Incorrect value for shipping state returned", stateValue, acct.getField("ShippingState")); + assertEquals("Incorrect value for description returned", stateValue, acct.getField("Description")); + } + } + } + + @Test + public void testEmptyFirstRowFieldValueInCsv() throws Exception { + Map argumentMap = getUpdateTestConfig(false, null, 2); + if (isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_ZIP_CONTENT) + || isSettingEnabled(argumentMap, AppConfig.PROP_PROCESS_BULK_CACHE_DATA_FROM_DAO) + || isSettingEnabled(argumentMap, AppConfig.PROP_BULK_API_SERIAL_MODE) + || isSettingEnabled(argumentMap, AppConfig.PROP_NO_COMPRESSION) + || isSettingEnabled(argumentMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID) + ) { + return; + } + // update 2 records + Controller controller = runProcess(argumentMap, 2); + + for (SObject acct : retrieveAccounts(controller, "Name", "Website")) { + String websiteVal = (String)acct.getField("Website"); + String acctName = (String)acct.getField("Name"); + if ("account Update #1".equals(acctName)) { + assertEquals("Incorrect value for field Website returned for the account " + acctName, "updated", websiteVal); + } + } } private void runTestErrorsGeneratedOnInvalidDateMatchWithOffset(Integer rowOffset, final int numSuccesses, final int numFailures) throws Exception { @@ -416,11 +766,14 @@ private void runTestErrorsGeneratedOnInvalidDateMatchWithOffset(Integer rowOffse TimeZone TZ = TimeZone.getTimeZone("GMT"); - DateConverter converter = new DateConverter(TZ, false); + DateTimeConverter converter = new DateTimeConverter(TZ, false); //find the csv file Map argumentMap = getTestConfig(OperationInfo.insert, getTestDataDir() + "/timeZoneFormatTestingWithErrors.csv", false); - argumentMap.put(Config.LOAD_ROW_TO_START_AT, rowOffset.toString()); + argumentMap.put(AppConfig.PROP_LOAD_ROW_TO_START_AT, rowOffset.toString()); + if (isSettingEnabled(argumentMap, AppConfig.PROP_UPDATE_WITH_EXTERNALID)) { + return; + } // insert into the account on the custom fields specified Controller controller = runProcessWithErrors(argumentMap, numSuccesses, numFailures); diff --git a/src/test/java/com/salesforce/dataloader/process/CsvProcessWithOffsetTest.java b/src/test/java/com/salesforce/dataloader/process/CsvProcessWithOffsetTest.java index d6fa468a6..9a1addb53 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvProcessWithOffsetTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvProcessWithOffsetTest.java @@ -37,12 +37,13 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.DataAccessObjectInitializationException; import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -67,7 +68,9 @@ public CsvProcessWithOffsetTest(Map config) { public static Collection getTestParameters() { return Arrays.asList( TestVariant.defaultSettings(), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED) + ); } /** @@ -139,14 +142,14 @@ private void runOffsetValueTest(Object offset, int numberOfInserts) throws Excep expectedUpdates); // now check offset specs - String rowOffset = ctl.getConfig().getString(Config.LOAD_ROW_TO_START_AT); + String rowOffset = ctl.getAppConfig().getString(AppConfig.PROP_LOAD_ROW_TO_START_AT); if (rowOffset != null) { - verifyOffsetFromInputAndOutputFiles(iOffset, ctl.getConfig()); + verifyOffsetFromInputAndOutputFiles(iOffset, ctl.getAppConfig()); } } - private void verifyOffsetFromInputAndOutputFiles(int numberOfOffsetRows, Config cfg) throws Exception { + private void verifyOffsetFromInputAndOutputFiles(int numberOfOffsetRows, AppConfig cfg) throws Exception { // Find out how many rows each file has int numberOfSuccessRows = 0; @@ -155,15 +158,15 @@ private void verifyOffsetFromInputAndOutputFiles(int numberOfOffsetRows, Config // finding rows in input file and opening it - numberOfInputRows = getNumCsvRows(cfg, Config.DAO_NAME); + numberOfInputRows = getNumCsvRows(cfg, AppConfig.PROP_DAO_NAME); // finding rows in success file and opening it - CSVFileReader successFileReader = openConfiguredPath(cfg, Config.OUTPUT_SUCCESS); - numberOfSuccessRows = getNumCsvRows(cfg, Config.OUTPUT_SUCCESS); + CSVFileReader successFileReader = openConfiguredPath(cfg, AppConfig.PROP_OUTPUT_SUCCESS); + numberOfSuccessRows = getNumCsvRows(cfg, AppConfig.PROP_OUTPUT_SUCCESS); // finding rows in error file and opening it - CSVFileReader errorFileReader = openConfiguredPath(cfg, Config.OUTPUT_ERROR); - numberOfErrorRows = getNumCsvRows(cfg, Config.OUTPUT_ERROR); + CSVFileReader errorFileReader = openConfiguredPath(cfg, AppConfig.PROP_OUTPUT_ERROR); + numberOfErrorRows = getNumCsvRows(cfg, AppConfig.PROP_OUTPUT_ERROR); if (numberOfOffsetRows <= numberOfInputRows) { assertEquals("Number of lines between input and output do not match", numberOfInputRows, @@ -171,30 +174,30 @@ private void verifyOffsetFromInputAndOutputFiles(int numberOfOffsetRows, Config } // Initializations of row results - Row firstInputOffsetAdjustedRow = new Row(); - Row lastInputRow = new Row(); - Row firstSuccessRow = new Row(); - Row lastSuccessRow = new Row(); - Row firstErrorRow = new Row(); - Row lastErrorRow = new Row(); + TableRow firstInputOffsetAdjustedRow = null; + TableRow lastInputRow = null; + TableRow firstSuccessRow = null; + TableRow lastSuccessRow = null; + TableRow firstErrorRow = null; + TableRow lastErrorRow = null; // The next few if statements deal with the edge statements on file size...(i.e. suppose that there are no // errors) if (numberOfSuccessRows > 0) { - getFirstRow(firstSuccessRow, successFileReader, true, 0); - getLastRow(lastSuccessRow, successFileReader, true); + firstSuccessRow = getFirstRow(successFileReader, true, 0); + lastSuccessRow = getLastRow(successFileReader, true); } if (numberOfErrorRows > 0) { - getFirstRow(firstErrorRow, errorFileReader, false, 0); - getLastRow(lastErrorRow, errorFileReader, false); + firstErrorRow = getFirstRow(errorFileReader, false, 0); + lastErrorRow = getLastRow(errorFileReader, false); } if (numberOfInputRows > 0) { - final CSVFileReader inputFileReader = openConfiguredPath(cfg, Config.DAO_NAME); + final CSVFileReader inputFileReader = openConfiguredPath(cfg, AppConfig.PROP_DAO_NAME); - getFirstRow(firstInputOffsetAdjustedRow, inputFileReader, false, numberOfOffsetRows); - getLastRow(lastInputRow, inputFileReader, false); + firstInputOffsetAdjustedRow = getFirstRow(inputFileReader, false, numberOfOffsetRows); + lastInputRow = getLastRow(inputFileReader, false); } // Requirement I: First offset-adjusted row of input matches to either the error or success file's first row @@ -208,7 +211,7 @@ private void verifyOffsetFromInputAndOutputFiles(int numberOfOffsetRows, Config } //otherwise vacuously true } - private int getNumCsvRows(Config cfg, String setting) throws DataAccessObjectException { + private int getNumCsvRows(AppConfig cfg, String setting) throws DataAccessObjectException { final CSVFileReader rdr = openConfiguredPath(cfg, setting); try { return rdr.getTotalRows(); @@ -217,52 +220,39 @@ private int getNumCsvRows(Config cfg, String setting) throws DataAccessObjectExc } } - private CSVFileReader openConfiguredPath(Config cfg, String configSetting) + private CSVFileReader openConfiguredPath(AppConfig cfg, String configSetting) throws DataAccessObjectInitializationException { - final CSVFileReader rdr = new CSVFileReader(new File(cfg.getString(configSetting)), cfg); + final CSVFileReader rdr = new CSVFileReader(new File(cfg.getString(configSetting)), cfg, false, false); rdr.open(); return rdr; } - private void getFirstRow(Row rowResult, CSVFileReader reader, boolean isSuccessFile, int rowOffset) + private TableRow getFirstRow(CSVFileReader reader, boolean isSuccessFile, int rowOffset) throws Exception { - Row firstRow = reader.readRow(); + TableRow firstRow = reader.readTableRow(); for (int i = 0; i < rowOffset; i++) { - firstRow = reader.readRow(); // then, for each, move down one row - } - - if (isSuccessFile) { - // Also ask for ID - rowResult.put("ID", firstRow.get("ID")); - } - if (firstRow != null && firstRow.get("NAME") != null) { - rowResult.put("NAME", firstRow.get("NAME")); + firstRow = reader.readTableRow(); // then, for each, move down one row } + return firstRow; } - private void getLastRow(Row rowResult, CSVFileReader reader, boolean isSuccessFile) + private TableRow getLastRow(CSVFileReader reader, boolean isSuccessFile) throws Exception { - Row tempRow = new Row(); - Row lastRow = new Row(); + TableRow tempRow = null; + TableRow lastRow = null; // get to the last row: - while ((tempRow = reader.readRow()) != null) { + while ((tempRow = reader.readTableRow()) != null) { lastRow = tempRow; } - - if (isSuccessFile) { - // Also ask for ID - rowResult.put("ID", lastRow.get("ID")); - } - - rowResult.put("NAME", lastRow.get("NAME")); + return lastRow; } private Map getRowOffsetTestConfig(Object offset, int numInserts) throws DataAccessObjectException { final Map argMap = getUpdateTestConfig(FILE_NAME_BASE, true, DEFAULT_ACCOUNT_EXT_ID_FIELD, numInserts); - argMap.put(Config.LOAD_ROW_TO_START_AT, offset.toString()); + argMap.put(AppConfig.PROP_LOAD_ROW_TO_START_AT, offset.toString()); return argMap; } } diff --git a/src/test/java/com/salesforce/dataloader/process/CsvUpsertProcessTest.java b/src/test/java/com/salesforce/dataloader/process/CsvUpsertProcessTest.java index e44e85325..0cb6b689d 100644 --- a/src/test/java/com/salesforce/dataloader/process/CsvUpsertProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/CsvUpsertProcessTest.java @@ -28,7 +28,7 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -54,7 +54,10 @@ public CsvUpsertProcessTest(Map config) { public static Collection getTestParameters() { return Arrays.asList( TestVariant.defaultSettings(), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED) + ); } /** @@ -66,7 +69,7 @@ public void testUpsertWithRowOffset() throws Exception { Map argMap = getUpdateTestConfig(true, DEFAULT_ACCOUNT_EXT_ID_FIELD, 10); // start at 3, not 0!! - argMap.put(Config.LOAD_ROW_TO_START_AT, "3"); + argMap.put(AppConfig.PROP_LOAD_ROW_TO_START_AT, "3"); // perform the upsert runUpsertProcess(argMap, 0, 7); diff --git a/src/test/java/com/salesforce/dataloader/process/DatabaseProcessTest.java b/src/test/java/com/salesforce/dataloader/process/DatabaseProcessTest.java index d28c381dc..013fdea35 100644 --- a/src/test/java/com/salesforce/dataloader/process/DatabaseProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/DatabaseProcessTest.java @@ -31,7 +31,9 @@ import java.text.ParseException; import java.util.*; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import com.salesforce.dataloader.util.DLLogManager; + import org.junit.*; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -39,13 +41,13 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.config.LastRun; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.config.LastRunProperties; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.database.DatabaseReader; import com.salesforce.dataloader.dao.database.DatabaseTestUtil; import com.salesforce.dataloader.exception.*; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableRow; /** * Automated tests for dataloader database batch interface @@ -56,7 +58,7 @@ @RunWith(Parameterized.class) public class DatabaseProcessTest extends ProcessTestBase { - private static final Logger logger = Logger.getLogger(DatabaseReader.class); + private static final Logger logger = DLLogManager.getLogger(DatabaseReader.class); private static final int NUM_ROWS = 1000; private static final int BATCH_SIZE = 100; @@ -69,8 +71,11 @@ public static Collection getTestParameters() { return Arrays.asList( TestVariant.defaultSettings(), TestVariant.forSettings(TestSetting.BULK_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED), TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_ZIP_CONTENT_ENABLED), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_SERIAL_MODE_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_ENABLED) + ); } @Before @@ -125,7 +130,7 @@ public void testUpsertAccountDb() throws Exception { // This test happens to configure its own encryption file and encrypted password // so we need to remove the default test password from the config final Map argMap = getTestConfig(); - argMap.remove(Config.PASSWORD); + argMap.remove(AppConfig.PROP_PASSWORD); // insert testUpsertAccountsDb(argMap, NUM_ROWS, true, false); // update @@ -141,10 +146,10 @@ private void testUpsertAccountsDb(Map args, int numRows, boolean // specify the name of the configured process and select appropriate database access type if (args == null) args = getTestConfig(); - args.put(ProcessRunner.PROCESS_NAME, processName); - Config.DATE_FORMATTER.parse(startTime); - args.put(LastRun.LAST_RUN_DATE, startTime); - args.put(Config.OPERATION, OperationInfo.upsert.name()); + args.put(AppConfig.PROP_PROCESS_NAME, processName); + AppConfig.DATE_FORMATTER.parse(startTime); + args.put(LastRunProperties.LAST_RUN_DATE, startTime); + args.put(AppConfig.PROP_OPERATION, OperationInfo.upsert.name()); runUpsertProcess(args, isInsert ? numRows : 0, isInsert ? 0 : numRows); } @@ -152,18 +157,18 @@ private void testUpsertAccountsDb(Map args, int numRows, boolean @Test public void testInsertNullsDB() throws Exception { Map args = getTestConfig(); - if (isBulkAPIEnabled(args)) { + if (isBulkAPIEnabled(args) || isBulkV2APIEnabled(args)) { logger.info("testInsertNulls is disabled for bulk api"); return; } // TODO: we need to get the accounts from sfdc and check that field values were updated correctly // create some rows with non-null values in them - args.put(Config.INSERT_NULLS, Boolean.toString(false)); + args.put(AppConfig.PROP_INSERT_NULLS, Boolean.toString(false)); testUpsertAccountsDb(args, 10, true, false); // update the rows with some null values, but with insert nulls disabled testUpsertAccountsDb(args, 10, false, true); // update the rows with some null values, but with insert nulls enabled - args.put(Config.INSERT_NULLS, Boolean.toString(true)); + args.put(AppConfig.PROP_INSERT_NULLS, Boolean.toString(true)); testUpsertAccountsDb(args, 10, false, true); } @@ -173,15 +178,15 @@ private void doExtractAccountDb(String processName, int expectedSuccesses, int e // specify the name of the configured process and select appropriate database access type OperationInfo op = isInsert ? OperationInfo.insert : OperationInfo.update; Map argMap = getTestConfig(); - argMap.put(Config.OPERATION, OperationInfo.extract.name()); - argMap.put(ProcessRunner.PROCESS_NAME, processName); - argMap.put(Config.DAO_NAME, op.name() + "Account"); - argMap.put(Config.OUTPUT_SUCCESS, new File(getTestStatusDir(), baseName + op.name() + "Success.csv") + argMap.put(AppConfig.PROP_OPERATION, OperationInfo.extract.name()); + argMap.put(AppConfig.PROP_PROCESS_NAME, processName); + argMap.put(AppConfig.PROP_DAO_NAME, op.name() + "Account"); + argMap.put(AppConfig.PROP_OUTPUT_SUCCESS, new File(getTestStatusDir(), baseName + op.name() + "Success.csv") .getAbsolutePath()); - argMap.put(Config.OUTPUT_ERROR, new File(getTestStatusDir(), baseName + op.name() + "Error.csv") + argMap.put(AppConfig.PROP_OUTPUT_ERROR, new File(getTestStatusDir(), baseName + op.name() + "Error.csv") .getAbsolutePath()); - argMap.put(Config.ENABLE_EXTRACT_STATUS_OUTPUT, Config.TRUE); - argMap.put(Config.DAO_WRITE_BATCH_SIZE, String.valueOf(BATCH_SIZE)); + argMap.put(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT, AppConfig.TRUE); + argMap.put(AppConfig.PROP_DAO_WRITE_BATCH_SIZE, String.valueOf(BATCH_SIZE)); Date startTime = new Date(); @@ -197,17 +202,17 @@ private void verifyDbSuccess(Controller theController, String dbConfigName, int DatabaseReader reader = null; logger.info("Verifying database success for database configuration: " + dbConfigName); try { - reader = new DatabaseReader(theController.getConfig(), dbConfigName); + reader = new DatabaseReader(theController.getAppConfig(), dbConfigName); reader.open(); - int readBatchSize = theController.getConfig().getInt(Config.DAO_READ_BATCH_SIZE); - List successRows = reader.readRowList(readBatchSize); + int readBatchSize = theController.getAppConfig().getInt(AppConfig.PROP_DAO_READ_BATCH_SIZE); + List successRows = reader.readTableRowList(readBatchSize); int rowsProcessed = 0; assertNotNull("Error reading " + readBatchSize + " rows", successRows); while(successRows.size() > 0) { rowsProcessed += successRows.size(); logger.info("Verifying database success for next " + successRows.size() + " of total " + rowsProcessed + " rows"); assertTrue("No updated rows have been found in the database.", successRows.size() > 0); - successRows = reader.readRowList(readBatchSize); + successRows = reader.readTableRowList(readBatchSize); } assertEquals(expectedSuccesses, rowsProcessed); } catch (DataAccessObjectInitializationException e) { diff --git a/src/test/java/com/salesforce/dataloader/process/DateOnlyProcessTest.java b/src/test/java/com/salesforce/dataloader/process/DateOnlyProcessTest.java new file mode 100644 index 000000000..8bf72faab --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/process/DateOnlyProcessTest.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.process; + +import com.salesforce.dataloader.action.OperationInfo; +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.sforce.soap.partner.QueryResult; + +import org.junit.Test; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Map; +import java.util.TimeZone; + +import static org.junit.Assert.assertEquals; + +/** + * Tests date-only values used in DataLoader processes + * + */ +public class DateOnlyProcessTest extends ProcessTestBase { + + private static final TimeZone GMT_TIME_ZONE = TimeZone.getTimeZone("GMT"); + private final DateFormat partnerApiDateFormat; + + public DateOnlyProcessTest() { + super(); + partnerApiDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); + partnerApiDateFormat.setTimeZone(GMT_TIME_ZONE); + } + + @Override + protected Map getTestConfig() { + Map cfg = super.getTestConfig(); + cfg.put(AppConfig.PROP_ENTITY, "Account"); + return cfg; + } + + @Test + public void testDateOnlyWithTimeZone() throws Exception { + Map testConfig = getTestConfig(OperationInfo.insert, false); + + // need to do this before calling runProcess to avoid incorrect timezone setting for DateOnlyConverter + testConfig.put(AppConfig.PROP_TIMEZONE, "IST"); + + System.out.println("===== DateOnlyProcessTest.testDateWithTimeZone: going to call runProcess with timezone=IST"); + + // need to do this before calling runProcess to avoid incorrect timezone setting for DateOnlyConverter + Controller controller = runProcess(testConfig, 2); + String tz = controller.getAppConfig().getString(AppConfig.PROP_TIMEZONE); + System.out.println("===== DateOnlyProcessTest.testDateWithTimeZone: configured timezone before first query is " + tz); + QueryResult qr = getBinding().query("select CustomDateOnly__c from Account where AccountNumber__c='ACCT_0'"); + assertEquals(1, qr.getSize()); + + // 1st entry specifies the date-only field in Zulu format + // 2010-10-14T00:00:00Z + assertEquals("2010-10-14", (String)qr.getRecords()[0].getField("CustomDateOnly__c")); + + qr = getBinding().query("select CustomDateOnly__c from Account where AccountNumber__c='ACCT_1'"); + assertEquals(1, qr.getSize()); + + // 2nd entry specifies the date-only field without 'Z' + tz = controller.getAppConfig().getString(AppConfig.PROP_TIMEZONE); + System.out.println("===== DateOnlyProcessTest.testDateWithTimeZone: configured timezone before 2nd query is " + tz); + assertEquals("2010-10-14", (String)qr.getRecords()[0].getField("CustomDateOnly__c")); + + } +} \ No newline at end of file diff --git a/src/test/java/com/salesforce/dataloader/process/DateProcessTest.java b/src/test/java/com/salesforce/dataloader/process/DateProcessTest.java index 8fb22aa8f..e613bd8ca 100644 --- a/src/test/java/com/salesforce/dataloader/process/DateProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/DateProcessTest.java @@ -29,7 +29,7 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.sforce.soap.partner.QueryResult; import org.junit.Test; import org.junit.runner.RunWith; @@ -70,15 +70,17 @@ public DateProcessTest(Map config) { public static Collection getTestParameters() { return Arrays.asList( TestVariant.defaultSettings(), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED), + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED) + ); } @Override protected Map getTestConfig() { Map cfg = super.getTestConfig(); - cfg.put(Config.TIMEZONE, "PDT"); - cfg.put(Config.ENTITY, "Account"); + cfg.put(AppConfig.PROP_TIMEZONE, "PDT"); + cfg.put(AppConfig.PROP_ENTITY, "Account"); return cfg; } @@ -105,7 +107,7 @@ public void testDateUsingDefaultTimeZone() throws Exception { @Test public void testDateWithTimeZone() throws Exception { - runProcess(getTestConfig(OperationInfo.insert, false), 1); + runProcess(getTestConfig(OperationInfo.insert, false), 2); QueryResult qr = getBinding().query("select CustomDateTime__c from Account where AccountNumber__c='ACCT_0'"); assertEquals(1, qr.getSize()); diff --git a/src/test/java/com/salesforce/dataloader/process/NAProcessTest.java b/src/test/java/com/salesforce/dataloader/process/NAProcessTest.java index 4bead0b16..eebee5640 100644 --- a/src/test/java/com/salesforce/dataloader/process/NAProcessTest.java +++ b/src/test/java/com/salesforce/dataloader/process/NAProcessTest.java @@ -34,6 +34,7 @@ import org.junit.Assert; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; @@ -41,12 +42,14 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.dao.csv.CSVFileWriter; import com.salesforce.dataloader.model.NATextValue; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.TableHeader; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; import com.sforce.soap.partner.QueryResult; import com.sforce.soap.partner.SaveResult; import com.sforce.soap.partner.sobject.SObject; @@ -86,8 +89,10 @@ public void populateUserId() throws Exception { @Parameterized.Parameters(name = "{0}") public static Collection getConfigGeneratorParams() { return Arrays.asList( + TestVariant.forSettings(TestSetting.BULK_API_DISABLED), TestVariant.forSettings(TestSetting.BULK_API_ENABLED), - TestVariant.forSettings(TestSetting.BULK_API_DISABLED)); + TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_API_CACHE_DAO_UPLOAD_ENABLED) + ); } @Test @@ -148,9 +153,10 @@ private void runNAtest(String nullFieldName, boolean isDateField, OperationInfo generateCsvWithNAField(nullFieldName, taskId); Map argMap = getArgMap(operation); Controller controller; - if (!getController().getConfig().getBoolean(Config.BULK_API_ENABLED) && isDateField) { + AppConfig appConfig = getController().getAppConfig(); + if (!appConfig.isBulkAPIEnabled() && !appConfig.isBulkV2APIEnabled() && isDateField) { controller = runProcess(argMap, true, null, 0, 0, 1, false); - String errorFile = controller.getConfig().getStringRequired(Config.OUTPUT_ERROR); + String errorFile = controller.getAppConfig().getStringRequired(AppConfig.PROP_OUTPUT_ERROR); String errorMessage = getCsvFieldValue(errorFile, "ERROR"); assertEquals("unexpected error message", "Error converting value to correct data type: Failed to parse date: #N/A", errorMessage); @@ -159,7 +165,7 @@ private void runNAtest(String nullFieldName, boolean isDateField, OperationInfo int numUpdate = operation.equals(OperationInfo.update) ? 1 : 0; controller = runProcess(argMap, true, null, numInsert, numUpdate, 0, false); String actualNullFieldValue = getFieldValueAfterOperation(nullFieldName, controller); - String expectedNullFieldValue = getController().getConfig().getBoolean(Config.BULK_API_ENABLED) ? null : NATextValue.getInstance().toString(); + String expectedNullFieldValue = getController().getAppConfig().getBoolean(AppConfig.PROP_BULK_API_ENABLED) ? null : NATextValue.getInstance().toString(); assertEquals("unexpected field value", expectedNullFieldValue, actualNullFieldValue); } } @@ -180,7 +186,7 @@ private void runEmptyFieldUpdateTest(String nullFieldName, boolean isDateField) } private String getFieldValueAfterOperation(String nullFieldName, Controller controller) throws Exception { - String successFile = controller.getConfig().getStringRequired(Config.OUTPUT_SUCCESS); + String successFile = controller.getAppConfig().getStringRequired(AppConfig.PROP_OUTPUT_SUCCESS); String taskId = getCsvFieldValue(successFile, "ID"); QueryResult result = getController().getPartnerClient().query("select " + nullFieldName + " from Task where Id='" + taskId + "'"); assertEquals(1, result.getSize()); @@ -189,8 +195,8 @@ private String getFieldValueAfterOperation(String nullFieldName, Controller cont private Map getArgMap(OperationInfo operation) { Map argMap = getTestConfig(operation, CSV_FILE_PATH, getTestDataDir() + File.separator + "NAProcessTest.sdl", false); - argMap.put(Config.ENTITY, "Task"); - argMap.remove(Config.EXTERNAL_ID_FIELD); + argMap.put(AppConfig.PROP_ENTITY, "Task"); + argMap.remove(AppConfig.PROP_IDLOOKUP_FIELD); return argMap; } @@ -201,7 +207,7 @@ private String createTask(String fieldToNullName, boolean isDateField) throws Ex task.setField("OwnerId", userId); task.setField("Subject", TASK_SUBJECT); task.setField(fieldToNullName, fieldToNullValue); - SaveResult[] result = getController().getPartnerClient().getClient().create(new SObject[] { task }); + SaveResult[] result = getController().getPartnerClient().getConnection().create(new SObject[] { task }); assertEquals(1, result.length); if (!result[0].getSuccess()) Assert.fail("creation of task failed with error " + result[0].getErrors()[0].getMessage()); @@ -209,17 +215,17 @@ private String createTask(String fieldToNullName, boolean isDateField) throws Ex } private String getCsvFieldValue(String csvFile, String fieldName) throws Exception { - CSVFileReader reader = new CSVFileReader(csvFile, getController(), false); + CSVFileReader reader = new CSVFileReader(new File(csvFile), getController().getAppConfig(), true, false); reader.open(); assertEquals(1, reader.getTotalRows()); - String fieldValue = (String)reader.readRow().get(fieldName); + String fieldValue = (String)reader.readTableRow().get(fieldName); reader.close(); return fieldValue; } private String getUserId() throws Exception { QueryResult result = getController().getPartnerClient().query( - "select id from user where username='" + getController().getConfig().getString(Config.USERNAME) + "'"); + "select id from user where username='" + getController().getAppConfig().getString(AppConfig.PROP_USERNAME) + "'"); assertEquals(1, result.getSize()); return result.getRecords()[0].getId(); } @@ -247,7 +253,13 @@ private void generateCsv(String nullFieldName, Object nullFieldValue, String id) assertTrue("Could not delete existing CSV file: " + CSV_FILE_PATH, deleteCsvFileOk); } - Row row = new Row(); + ArrayList headerLabelList = new ArrayList(); + headerLabelList.add("OwnerId"); + headerLabelList.add("Subject"); + if (id != null) headerLabelList.add("Id"); + headerLabelList.add(nullFieldName); + TableHeader header = new TableHeader(headerLabelList); + TableRow row = new TableRow(header); row.put("OwnerId", userId); row.put("Subject", TASK_SUBJECT); row.put(nullFieldName, nullFieldValue); @@ -255,18 +267,12 @@ private void generateCsv(String nullFieldName, Object nullFieldValue, String id) CSVFileWriter writer = null; try { - writer = new CSVFileWriter(CSV_FILE_PATH, getController().getConfig()); + writer = new CSVFileWriter(CSV_FILE_PATH, getController().getAppConfig(), AppUtil.COMMA); writer.open(); - writer.setColumnNames(new ArrayList(row.keySet())); + writer.setColumnNames(new ArrayList(header.getColumns())); writer.writeRow(row); } finally { if (writer != null) writer.close(); } } - - @Override - public void cleanRecords() { - deleteSfdcRecords("Task", "Subject='" + TASK_SUBJECT + "'", 0); - } - } diff --git a/src/test/java/com/salesforce/dataloader/process/ProcessExtractTestBase.java b/src/test/java/com/salesforce/dataloader/process/ProcessExtractTestBase.java index 5bc9efa78..ae02331cf 100644 --- a/src/test/java/com/salesforce/dataloader/process/ProcessExtractTestBase.java +++ b/src/test/java/com/salesforce/dataloader/process/ProcessExtractTestBase.java @@ -28,6 +28,7 @@ import static org.junit.Assert.*; +import java.io.File; import java.util.*; import org.junit.Assert; @@ -36,14 +37,13 @@ import com.salesforce.dataloader.TestSetting; import com.salesforce.dataloader.TestVariant; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataReader; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.exception.DataAccessObjectException; import com.salesforce.dataloader.exception.ProcessInitializationException; -import com.salesforce.dataloader.model.Row; -import com.sforce.soap.partner.SaveResult; +import com.salesforce.dataloader.model.TableRow; import com.sforce.soap.partner.sobject.SObject; import com.sforce.ws.ConnectionException; @@ -62,8 +62,13 @@ public ProcessExtractTestBase(Map config) { @Parameterized.Parameters(name = "{0}") public static Collection getParameters() { return Arrays.asList( - TestVariant.defaultSettings(), - TestVariant.forSettings(TestSetting.BULK_API_ENABLED)); + // partner API + TestVariant.forSettings(TestSetting.BULK_API_DISABLED, TestSetting.BULK_V2_API_DISABLED) + // Bulk API + , TestVariant.forSettings(TestSetting.BULK_API_ENABLED, TestSetting.BULK_V2_API_DISABLED) + // Bulk V2 Query API + , TestVariant.forSettings(TestSetting.BULK_V2_API_ENABLED) + ); } protected class ExtractContactGenerator extends ContactGenerator { @@ -112,37 +117,72 @@ public String getSOQL(String selectExpression) { protected abstract boolean isExtractAll(); - protected Map getTestConfig(String soql, String entity, boolean useMappingFile) { + protected Map getExtractionTestConfig(String soql, String entity, boolean useMappingFile) { final Map argMap = getTestConfig(isExtractAll() ? OperationInfo.extract_all : OperationInfo.extract, true); - argMap.put(Config.ENTITY, entity); - argMap.put(Config.EXTRACT_SOQL, soql); - argMap.put(Config.ENABLE_EXTRACT_STATUS_OUTPUT, Config.TRUE); - argMap.put(Config.EXTRACT_REQUEST_SIZE, "2000"); + argMap.put(AppConfig.PROP_ENTITY, entity); + argMap.put(AppConfig.PROP_EXTRACT_SOQL, soql); + argMap.put(AppConfig.PROP_ENABLE_EXTRACT_STATUS_OUTPUT, AppConfig.TRUE); + argMap.put(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS, AppConfig.TRUE); + argMap.put(AppConfig.PROP_EXPORT_BATCH_SIZE, "2000"); if (!useMappingFile) { - argMap.remove(Config.MAPPING_FILE); + argMap.remove(AppConfig.PROP_MAPPING_FILE); } return argMap; } + + Map getDoNotLimitOutputToQueryFieldsTestConfig(String soql, String entity, boolean useMappingFile) { + final Map argMap = getExtractionTestConfig(soql, entity, useMappingFile); + argMap.put(AppConfig.PROP_LIMIT_OUTPUT_TO_QUERY_FIELDS, AppConfig.FALSE); + return argMap; + } + // Utility functions - protected void verifyIdsInCSV(Controller control, String[] ids) throws DataAccessObjectException { + verifyIdsInCSV(control, ids, false); + } + + protected void verifyIdsInCSV(Controller control, String[] ids, boolean checkPhoneFormat) throws DataAccessObjectException { // assert that it's a CSV...if not fail final Set unexpectedIds = new HashSet(); final Set expectedIds = new HashSet(Arrays.asList(ids)); - String fileName = control.getConfig().getString(Config.OUTPUT_SUCCESS); - final DataReader resultReader = new CSVFileReader(fileName, getController()); + String fileName = control.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + final DataReader resultReader = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); try { resultReader.open(); // go through item by item and assert that it's there - Row row; - while ((row = resultReader.readRow()) != null) { - final String resultId = (String)row.get(Config.ID_COLUMN_NAME); + TableRow row; + int currentRow = 0; + while ((row = resultReader.readTableRow()) != null) { + final String resultId = (String)row.get(AppConfig.ID_COLUMN_NAME); assertValidId(resultId); + String resultPhone = (String)row.get("Phone"); + if (checkPhoneFormat && resultPhone != null) { + resultPhone = resultPhone.substring(0, 8); + int remainder = currentRow++ % 4; + switch (remainder) { + case 0 : + assertEquals("Incorrect phone number conversion", resultPhone, "+1415555"); + break; + case 1 : + assertEquals("Incorrect phone number conversion", resultPhone, "(415) 55"); + break; + case 2 : + assertEquals("Incorrect phone number conversion", resultPhone, "(415) 55"); + break; + case 3 : + resultPhone = resultPhone.substring(0,5); + assertEquals("Incorrect phone number conversion", resultPhone, "14155"); + break; + default : + assertEquals("Incorrect phone number conversion", resultPhone, "1415555"); + break; + } + } if (!expectedIds.remove(resultId)) { unexpectedIds.add(resultId); } @@ -174,9 +214,9 @@ protected void runTestNestedQueryErrorsCorrectly() throws ProcessInitializationE String soql = null; Map argmap = null; soql = "Select Account.Name, (Select Contact.LastName FROM Account.Contacts) FROM Account"; - argmap = getTestConfig(soql, "Account", false); + argmap = getExtractionTestConfig(soql, "Account", false); // this error message to change - runProcessNegative(argmap, "Invalid soql: Nested queries are not supported"); + runProcessNegative(argmap, "Invalid soql: Nested queries are not supported in SOQL SELECT clause"); } public abstract void testSoqlWithRelationships() throws Exception; @@ -210,6 +250,52 @@ public SObject getObject(int i, boolean negativeTest) { runSoqlRelationshipTest(contactId, accountId, "Select c.Id, C.Name, TestField__r.TestField__c, CONTACT.account.NAME, c.account.Id From Contact c Where Id = '" + contactId + "'"); } + + protected void runTestSelectFieldsSoql() throws ProcessInitializationException, + DataAccessObjectException { + + final TestFieldGenerator testFieldGen = new TestFieldGenerator(); + final String[] testFieldIds = insertSfdcRecords(1, false, testFieldGen); + + // TEST only if it is Partner or Bulk v2 + // set batch process parameters + if (!isBulkAPIEnabled(this.getTestConfig()) || isBulkV2APIEnabled(this.getTestConfig())) { + String soql = "SELECT fields(standard) FROM TestField__c WHERE id='" + testFieldIds[0] + "'"; // fields are not explicitly specified in SOQL + Map testConfig = getDoNotLimitOutputToQueryFieldsTestConfig(soql, "Account", true); + Controller control = runProcess(testConfig, 1); + // verify IDs and phone format + verifyIdsInCSV(control, testFieldIds, false); + String fileName = control.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + final DataReader resultReader = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); + try { + resultReader.open(); + + // go through item by item and assert that it's there + TableRow row; + int rowIdx = 0; + while ((row = resultReader.readTableRow()) != null) { + final String resultId = (String)row.get(AppConfig.ID_COLUMN_NAME); + assertValidId(resultId); + assertEquals(resultId, testFieldIds[rowIdx]); + + final String resultName = (String)row.get("name_to_test"); + assertTrue("Name field not mapped to DAO name in success file", + resultName != null && !resultName.isBlank() && resultName.startsWith("testfield__")); + rowIdx++; + } + } finally { + resultReader.close(); + } + + } + // cleanup + try { + getBinding().delete(testFieldIds); + } catch (ConnectionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } public abstract void testSoqlWithTableNameInSelect() throws Exception; @@ -217,16 +303,10 @@ protected void runTestSoqlWithTableNameInSelect() throws ProcessInitializationEx ConnectionException { final ExtractContactGenerator contactGenerator = new ExtractContactGenerator(); final String soql = contactGenerator.getSOQL("Contact.Id, Contact.Account.id"); - final Map argmap = getTestConfig(soql, "Contact", false); - if (isBulkAPIEnabled(argmap)) { - runProcessNegative( - argmap, - "Batch failed: InvalidBatch : Failed to process query: FUNCTIONALITY_NOT_ENABLED: Foreign Key Relationships not supported in Bulk Query"); - } else { - final String[] contactIds = insertExtractTestRecords(10, contactGenerator); - Controller control = runProcess(argmap, contactIds.length); - verifyIdsInCSV(control, contactIds); - } + final Map argmap = getExtractionTestConfig(soql, "Contact", false); + final String[] contactIds = insertExtractTestRecords(10, contactGenerator); + Controller control = runProcess(argmap, contactIds.length); + verifyIdsInCSV(control, contactIds); } // Tests common to both that need to be implemented @@ -236,10 +316,15 @@ protected void runTestForNonQueryableSObjects() throws ProcessInitializationExce final String nonQueryableType = "AggregateResult"; final String soql = "select id from " + nonQueryableType; - final Map argmap = getTestConfig(soql, nonQueryableType, false); - argmap.put(Config.OPERATION, OperationInfo.extract_all.name()); + final Map argmap = getExtractionTestConfig(soql, nonQueryableType, false); - runProcessNegative(argmap, "entity type " + nonQueryableType + " does not support query"); + if (isBulkV2APIEnabled(argmap) || !isBulkAPIEnabled(argmap)) { + // Partner or Bulk v2 query + runProcessNegative(argmap, "entity type " + nonQueryableType + " does not support query"); + } else { + // Bulk v1 query + runProcessNegative(argmap, "Entity '" + nonQueryableType + "' is not supported by the Bulk API."); + } } public abstract void testMalformedQueries() throws Exception; @@ -260,33 +345,34 @@ protected void runMalformedQueriesTest() throws ProcessInitializationException, private void runExtractNegativeTest(String soql, String expectedErrorMsg) throws ProcessInitializationException, DataAccessObjectException { - runProcessNegative(getTestConfig(soql, "Account", false), expectedErrorMsg); + runProcessNegative(getExtractionTestConfig(soql, "Account", false), expectedErrorMsg); } protected void runSoqlRelationshipTest(String contactId, String accountId, final String soql) throws ProcessInitializationException, DataAccessObjectException { + setServerApiInvocationThreshold(100); + Map argMap = getExtractionTestConfig(soql, "Contact", true); + doRunSoqlRelationshipTest(contactId, accountId, soql, argMap); + argMap = getDoNotLimitOutputToQueryFieldsTestConfig(soql, "Contact", true); + doRunSoqlRelationshipTest(contactId, accountId, soql, argMap); + } + + private void doRunSoqlRelationshipTest(String contactId, String accountId, final String soql, Map argMap) + throws ProcessInitializationException, DataAccessObjectException { - final Map argMap = getTestConfig(soql, "Contact", true); - - if (isBulkAPIEnabled(argMap)) { - runProcessNegative( - argMap, - "Batch failed: InvalidBatch : Failed to process query: FUNCTIONALITY_NOT_ENABLED: Foreign Key Relationships not supported in Bulk Query"); - } else { - runProcess(argMap, 1); - final CSVFileReader resultReader = new CSVFileReader(argMap.get(Config.DAO_NAME), getController()); - try { - final Row resultRow = resultReader.readRow(); - assertEquals("Query returned incorrect Contact ID", contactId, resultRow.get("CONTACT_ID")); - assertEquals("Query returned incorrect Contact Name", "First 000000 Last 000000", - resultRow.get("CONTACT_NAME")); - assertEquals("Query returned incorrect Account ID", accountId, resultRow.get("ACCOUNT_ID")); - assertEquals("Query returned incorrect Account Name", "account insert#000000", - resultRow.get("ACCOUNT_NAME")); + runProcess(argMap, 1); + final CSVFileReader resultReader = new CSVFileReader(new File(argMap.get(AppConfig.PROP_DAO_NAME)), getController().getAppConfig(), true, false); + try { + final TableRow resultRow = resultReader.readTableRow(); + assertEquals("Query returned incorrect Contact ID", contactId, resultRow.get("CONTACT_ID")); + assertEquals("Query returned incorrect Contact Name", "First 000000 Last 000000", + resultRow.get("CONTACT_NAME")); + assertEquals("Query returned incorrect Account ID", accountId, resultRow.get("ACCOUNT_ID")); + assertEquals("Query returned incorrect Account Name", "account insert#000000", + resultRow.get("ACCOUNT_NAME")); - } finally { - resultReader.close(); - } + } finally { + resultReader.close(); } } @@ -298,72 +384,63 @@ protected void runTestExtractAccountCsv() throws ProcessInitializationException, final ExtractAccountGenerator accountGen = new ExtractAccountGenerator(); final int numRecords = 100; final String[] accountIds = insertExtractTestRecords(numRecords, accountGen); - final String soql = accountGen - .getSOQL("ID, NAME, TYPE, PHONE, ACCOUNTNUMBER__C, WEBSITE, ANNUALREVENUE, LASTMODIFIEDDATE, ORACLE_ID__C"); - Controller control = runProcess(getTestConfig(soql, "Account", true), numRecords); - verifyIdsInCSV(control, accountIds); - } - - public void testPolymorphicRelationshipExtract() throws Exception { - // create a test lead - final String uid = getBinding().getUserInfo().getUserId(); - final String[] leadidArr = createLead(uid); + + String soql; + if (isBulkAPIEnabled(this.getTestConfig()) || isBulkV2APIEnabled(this.getTestConfig())) { + soql= accountGen + .getSOQL("ID, NAME, TYPE, PHONE, ACCOUNTNUMBER__C, WEBSITE, ANNUALREVENUE, LASTMODIFIEDDATE, ORACLE_ID__C"); + } else { + soql= accountGen + .getSOQL("ID, BILLINGADDRESS, NAME, TYPE, PHONE, ACCOUNTNUMBER__C, WEBSITE, ANNUALREVENUE, LASTMODIFIEDDATE, ORACLE_ID__C"); + + } + Map testConfig = getExtractionTestConfig(soql, "Account", true); + testConfig.put(AppConfig.PROP_DAO_WRITE_BATCH_SIZE, "10"); // total 100 entries in the results file, write in chunks of 10 + Controller control = runProcess(testConfig, numRecords); + // verify IDs and phone format + verifyIdsInCSV(control, accountIds, true); + + // Verify that column positions match positions + // specified in the mapping (.sdl) file. + String fileName = control.getAppConfig().getString(AppConfig.PROP_DAO_NAME); + final DataReader extractionReader = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); try { - final String soql = "SELECT Id, Owner.Name, Lead.Owner.Id, x.owner.lastname, OwnerId FROM Lead x where id='" - + leadidArr[0] + "'"; - final Map argmap = getTestConfig(soql, "Lead", true); - if (isBulkAPIEnabled(argmap)) { - // bulk api doesn't support foreign key relationships so it will always fail - final String expectedError = "Batch failed: InvalidBatch : Failed to process query: FUNCTIONALITY_NOT_ENABLED: Foreign Key Relationships not supported in Bulk Query"; - runProcessNegative( - argmap, - expectedError); + extractionReader.open(); + List daoCols = extractionReader.getColumnNames(); + String colAt = daoCols.get(4); + assertEquals("Incorrect DAO column sequence", colAt.toUpperCase(), "ACCOUNT_NAME"); + colAt = daoCols.get(5); + assertEquals("Incorrect DAO column sequence", colAt.toUpperCase(), "ACCOUNT_ID"); + + // Verify that column positions not specified in the mapping + // file are in the same order as specified in SOQL. + colAt = daoCols.get(7); + if (isBulkAPIEnabled(this.getTestConfig()) || isBulkV2APIEnabled(this.getTestConfig())) { + assertEquals("Incorrect DAO column sequence", colAt.toUpperCase(), "WEBSITE"); } else { - // run the extract - runProcess(argmap, 1); - // open the results of the extraction - final CSVFileReader rdr = new CSVFileReader(argmap.get(Config.DAO_NAME), getController()); - rdr.open(); - Row row = rdr.readRow(); - assertNotNull(row); - assertEquals(5,row.size()); - // validate the extract results are correct. - assertEquals(leadidArr[0], row.get("LID")); - assertEquals("loader", row.get("LNAME")); - assertEquals("data loader", row.get("NAME__RESULT")); - assertEquals(uid, row.get("OID")); - assertEquals(uid,row.get("OWNID")); - // validate that we have read the only result. there should be only one. - assertNull(rdr.readRow()); - + assertEquals("Incorrect DAO column sequence", colAt.toUpperCase(), "TYPE"); } } finally { - // cleanup here since the parent doesn't clean up leads - getBinding().delete(leadidArr); + extractionReader.close(); + } + + testConfig = getDoNotLimitOutputToQueryFieldsTestConfig(soql, "Account", true); + control = runProcess(testConfig, numRecords); + // verify IDs and phone format + verifyIdsInCSV(control, accountIds, true); + + if (!isBulkAPIEnabled(this.getTestConfig()) + && !isBulkV2APIEnabled(this.getTestConfig())) { + // Bulk v1 does not support Select fields() + // Bulk v2 supports Select fields but Account sobject's standard fields contain compound + // fields which are not supported by Bulk v2. + soql = accountGen + .getSOQL("fields(standard)"); // fields are not explicitly specified in SOQL + testConfig = getDoNotLimitOutputToQueryFieldsTestConfig(soql, "Account", true); + control = runProcess(testConfig, numRecords); + // verify IDs and phone format + verifyIdsInCSV(control, accountIds, true); } - - } - - /** creates a lead owned by the provided user */ - private String[] createLead(final String uid) throws ConnectionException { - final SObject lead = new SObject(); - // Create a lead sobject - lead.setType("Lead"); - lead.setField("LastName", "test lead"); - lead.setField("Company", "salesforce"); - lead.setField("OwnerId", uid); - - // insert the lead - final SaveResult[] result = getBinding().create(new SObject[] { lead }); - - // validate save result - assertNotNull(result); - assertEquals(1, result.length); - assertTrue(Arrays.toString(result[0].getErrors()), result[0].isSuccess()); - - // get new lead id - final String[] leadidArr = new String[] { result[0].getId() }; - return leadidArr; } public abstract void testExtractAccountCsvAggregate() throws Exception; @@ -376,16 +453,17 @@ protected void runTestExtractAccountCsvAggregate() throws ConnectionException, P insertExtractTestRecords(numRecords, accountGen); final String soql = accountGen.getSOQL("max(numberofemployees) max_emps"); - final Map argMap = getTestConfig(soql, "Account", false); - if (isBulkAPIEnabled(argMap)) { + final Map argMap = getExtractionTestConfig(soql, "Account", false); + if (isBulkAPIEnabled(argMap) || isBulkV2APIEnabled(this.getTestConfig())) { runProcessNegative( argMap, - "Batch failed: InvalidBatch : Failed to process query: FUNCTIONALITY_NOT_ENABLED: Aggregate Relationships not supported in Bulk Query"); + "Aggregate Relationships not supported in Bulk Query"); } else { runProcess(argMap, 1, true); - final CSVFileReader resultReader = new CSVFileReader(argMap.get(Config.DAO_NAME), getController()); + final CSVFileReader resultReader = new CSVFileReader(new File(argMap.get(AppConfig.PROP_DAO_NAME)), getController().getAppConfig(), true, false); try { - assertEquals(String.valueOf(numRecords - 1), resultReader.readRow().get("MAX(NUMBEROFEMPLOYEES)")); + String maxNumEmployees = (String)resultReader.readTableRow().get("MAX(NUMBEROFEMPLOYEES)"); + assertEquals(String.valueOf(numRecords - 1), maxNumEmployees); } finally { resultReader.close(); } @@ -400,11 +478,4 @@ protected String[] insertExtractTestRecords(int numRecords, SObjectGenerator sOb } return ids; } - - @Override - protected boolean isBulkAPIEnabled(Map argMap) { - // bulk api is not used for query all - return !isExtractAll() && super.isBulkAPIEnabled(argMap); - } - } diff --git a/src/test/java/com/salesforce/dataloader/process/ProcessTestBase.java b/src/test/java/com/salesforce/dataloader/process/ProcessTestBase.java index 69dccdf50..7bef59853 100644 --- a/src/test/java/com/salesforce/dataloader/process/ProcessTestBase.java +++ b/src/test/java/com/salesforce/dataloader/process/ProcessTestBase.java @@ -31,21 +31,32 @@ import java.io.*; import java.util.*; -import org.apache.log4j.Logger; +import org.apache.logging.log4j.Logger; +import org.apache.commons.beanutils.BasicDynaClass; +import org.apache.commons.beanutils.BeanUtils; +import org.apache.commons.beanutils.DynaBean; +import org.apache.commons.beanutils.DynaProperty; +import com.salesforce.dataloader.util.DLLogManager; import org.junit.Assert; -import org.junit.Before; import com.salesforce.dataloader.*; import com.salesforce.dataloader.action.OperationInfo; -import com.salesforce.dataloader.config.Config; +import com.salesforce.dataloader.action.progress.ILoaderProgress; +import com.salesforce.dataloader.client.HttpClientTransport; +import com.salesforce.dataloader.client.PartnerClient; +import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.dao.DataAccessObjectFactory; import com.salesforce.dataloader.dao.csv.CSVFileReader; import com.salesforce.dataloader.dao.csv.CSVFileWriter; +import com.salesforce.dataloader.dyna.SforceDynaBean; import com.salesforce.dataloader.exception.*; import com.salesforce.dataloader.exception.UnsupportedOperationException; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.model.RowInterface; +import com.salesforce.dataloader.model.TableRow; +import com.salesforce.dataloader.util.AppUtil; import com.salesforce.dataloader.util.Base64; +import com.salesforce.dataloader.action.progress.NihilistProgressAdapter; import com.sforce.soap.partner.*; import com.sforce.soap.partner.fault.ApiFault; import com.sforce.soap.partner.sobject.SObject; @@ -60,29 +71,25 @@ */ public abstract class ProcessTestBase extends ConfigTestBase { - private static Logger logger = Logger.getLogger(TestBase.class); + private static Logger logger = DLLogManager.getLogger(ProcessTestBase.class); + private int serverApiInvocationThreshold = 150; protected ProcessTestBase() { super(Collections.emptyMap()); + HttpClientTransport.resetServerInvocationCount(); } protected ProcessTestBase(Map config) { super(config); - } - - @Before - public void cleanRecords() { - // cleanup the records that might've been created on previous tests - deleteSfdcRecords("Account", ACCOUNT_WHERE_CLAUSE, 0); - deleteSfdcRecords("Contact", CONTACT_WHERE_CLAUSE, 0); + HttpClientTransport.resetServerInvocationCount(); } protected void verifyErrors(Controller controller, String expectedErrorMessage) throws DataAccessObjectException { - String fileName = controller.getConfig().getString(Config.OUTPUT_ERROR); - final CSVFileReader errReader = new CSVFileReader(fileName, controller); + String fileName = controller.getAppConfig().getString(AppConfig.PROP_OUTPUT_ERROR); + final CSVFileReader errReader = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); try { errReader.open(); - for (Row errorRow : errReader.readRowList(errReader.getTotalRows())) { + for (TableRow errorRow : errReader.readTableRowList(errReader.getTotalRows())) { String actualMessage = (String) errorRow.get("ERROR"); if (actualMessage == null || !actualMessage.startsWith(expectedErrorMessage)) Assert.fail("Error row does not have the expected error message: " + expectedErrorMessage @@ -98,12 +105,12 @@ protected void verifySuccessIds(Controller theController, String[] ids) throws D } protected void verifySuccessIds(Controller ctl, Set ids) throws DataAccessObjectException { - String fileName = ctl.getConfig().getString(Config.OUTPUT_SUCCESS); - final CSVFileReader successRdr = new CSVFileReader(fileName, ctl); + String fileName = ctl.getAppConfig().getString(AppConfig.PROP_OUTPUT_SUCCESS); + final CSVFileReader successRdr = new CSVFileReader(new File(fileName), ctl.getAppConfig(), true, false); final Set remaining = new HashSet(ids); final Set unexpected = new HashSet(); try { - for (Row row : successRdr.readRowList(Integer.MAX_VALUE)) { + for (TableRow row : successRdr.readTableRowList(Integer.MAX_VALUE)) { final String rowid = (String) row.get("ID"); if (rowid != null && rowid.length() > 0 && !remaining.remove(rowid)) unexpected.add(rowid); } @@ -329,8 +336,8 @@ private String[] upsertSfdcRecords(SObject[] records, boolean ignoreOutput, // get the client and make the insert call try { UpsertResult[] results = getBinding().upsert( - getController().getConfig().getString( - Config.EXTERNAL_ID_FIELD), records); + getController().getAppConfig().getString( + AppConfig.PROP_IDLOOKUP_FIELD), records); String[] ids = new String[results.length]; for (int i = 0; i < results.length; i++) { UpsertResult result = results[i]; @@ -420,7 +427,24 @@ public SObject getObject(int i, boolean negativeTest) { } account.setField("AccountNumber__c", accountNumberValue); account.setField("AnnualRevenue", (double) 1000 * i); - account.setField("Phone", "415-555-" + seqStr); + int remainder = i % 5; + switch (remainder) { + case 0: + account.setField("Phone", "+1415555" + seqStr); + break; + case 1: + account.setField("Phone", "415555" + seqStr); + break; + case 2: + account.setField("Phone", "1415555" + seqStr); + break; + case 3: // length less than 10 + account.setField("Phone", "14155" + seqStr); + break; + default: + account.setField("Phone", "141555567" + seqStr); + break; + } account.setField("WebSite", "http://www.accountInsert" + seqStr + ".com"); account.setField(DEFAULT_ACCOUNT_EXT_ID_FIELD, "1-" + seqStr); @@ -464,6 +488,7 @@ public SObject getObject(int i, boolean negativeTest) { contact.setField("Title", titleValue); contact.setField("Phone", "415-555-" + seqStr); contact.setField(DEFAULT_CONTACT_EXT_ID_FIELD, (double) i); + contact.setField("Email", "contact"+i+"@testcustomer.com"); return contact; } @@ -484,76 +509,39 @@ public String getSOQL(String selectFields) { } } - /** - * @param entityName - * @param whereClause - * @param retries - */ - protected void deleteSfdcRecords(String entityName, String whereClause, - int retries) { - try { - // query for records - String soql = "select Id from " + entityName + " where " + whereClause; - logger.debug("Querying " + entityName + "s to delete with soql: " + soql); - int deletedCount = 0; - PartnerConnection conn = getBinding(); - // now delete them 200 at a time.... we should use bulk api here - for (QueryResult qr = conn.query(soql); qr != null && qr.getRecords().length > 0; qr = qr.isDone() ? null - : conn.queryMore(qr.getQueryLocator())) { - deleteSfdcRecords(qr, 0); - deletedCount += qr.getRecords().length; - logger.debug("Deleted " + deletedCount + " out of " + qr.getSize() + " total deleted records"); - } - logger.info("Deleted " + deletedCount + " total objects of type " + entityName); - } catch (ApiFault e) { - if (checkBinding(++retries, e) != null) { - deleteSfdcRecords(entityName, whereClause, retries); - } - Assert.fail("Failed to query " + entityName + "s to delete (" - + whereClause + "), error: " + e.getExceptionMessage()); - } catch (ConnectionException e) { - Assert.fail("Failed to query " + entityName + "s to delete (" - + whereClause + "), error: " + e.getMessage()); + protected static class TestFieldGenerator extends AbstractSObjectGenerator { + /** + * @param i + * @return SObject contact + */ + @Override + public SObject getObject(int i, boolean negativeTest) { + String seqStr = String.format("%06d", i); + SObject testField = createSObject(); + testField.setField("Name", TESTFIELD_FIELD_PREFIX + seqStr); + testField.setField("TestField__c", TESTFIELD_FIELD_PREFIX + seqStr); + return testField; } - } - /** - * @param qryResult - */ - protected void deleteSfdcRecords(QueryResult qryResult, int retries) { - try { - List toDeleteIds = new ArrayList(); - for (int i = 0; i < qryResult.getRecords().length; i++) { - SObject record = qryResult.getRecords()[i]; - toDeleteIds.add(record.getId()); - // when SAVE_RECORD_LIMIT records are reached or - // if we're on the last query result record, do the delete - if (i > 0 && (i + 1) % SAVE_RECORD_LIMIT == 0 - || i == qryResult.getRecords().length - 1) { - DeleteResult[] delResults = getBinding().delete( - toDeleteIds.toArray(new String[] {})); - for (int j = 0; j < delResults.length; j++) { - DeleteResult delResult = delResults[j]; - if (!delResult.getSuccess()) { - logger.warn("Delete returned an error: " + delResult.getErrors()[0].getMessage(), - new RuntimeException()); - } - } - toDeleteIds.clear(); - } - } - } catch (ApiFault e) { - if (checkBinding(++retries, e) != null) { - deleteSfdcRecords(qryResult, retries); - } - Assert.fail("Failed to delete records, error: " + e.getExceptionMessage()); - } catch (ConnectionException e) { - Assert.fail("Failed to delete records, error: " + e.getMessage()); + /* + * (non-Javadoc) + * + * @seecom.salesforce.dataloader.process.ProcessTestBase.SObjectGetter# + * getEntityName() + */ + @Override + public String getEntityName() { + return "TestField__c"; + } + + @Override + public String getSOQL(String selectFields) { + return generateSOQL(selectFields, TESTFIELD_WHERE_CLAUSE); } } protected static interface TemplateListener { - void updateRow(int idx, Row row); + void updateRow(int idx, TableRow row); } /** @@ -568,8 +556,13 @@ public AccountIdTemplateListener(int numAccounts) { } @Override - public void updateRow(int idx, Row row) { + public void updateRow(int idx, TableRow row) { row.put("ID", idx < this.accountIds.length ? this.accountIds[idx] : ""); + for (String key : row.getHeader().getColumns()) { + if ("standard@org.com".equals(row.get(key))) { + row.put(key, getProperty("test.user.restricted")); + } + } } public String[] getAccountIds() { @@ -592,15 +585,15 @@ protected String convertTemplateToInput(String templateFileName, String inputFil TemplateListener... listeners) throws DataAccessObjectException { String fileName = new File(getTestDataDir(), templateFileName).getAbsolutePath(); - final CSVFileReader templateReader = new CSVFileReader(fileName, getController()); + final CSVFileReader templateReader = new CSVFileReader(new File(fileName), getController().getAppConfig(), true, false); try { templateReader.open(); int numRows = templateReader.getTotalRows(); - final List templateRows = templateReader.readRowList(numRows); + final List templateRows = templateReader.readTableRowList(numRows); assertNotNull("CVSReader returned a null list of rows, but expected a list with size " + numRows, templateRows); - final List inputRows = new ArrayList(templateRows.size()); + final List inputRows = new ArrayList(templateRows.size()); // verify that the template file is useable assertEquals("Wrong number of rows were read using readRowList while attempting to convert template file: " @@ -609,8 +602,8 @@ protected String convertTemplateToInput(String templateFileName, String inputFil // insert accounts for the whole template or part of it if // maxInserts is smaller then template size int idx = 0; - for (Row templateRow : templateRows) { - final Row row = new Row(templateRow); + for (TableRow templateRow : templateRows) { + final TableRow row = new TableRow(templateRow); if (listeners != null) { for (TemplateListener l : listeners) { l.updateRow(idx, row); @@ -620,7 +613,7 @@ protected String convertTemplateToInput(String templateFileName, String inputFil idx++; } final String inputPath = new File(getTestDataDir(), inputFileName).getAbsolutePath(); - final CSVFileWriter inputWriter = new CSVFileWriter(inputPath, getController().getConfig()); + final CSVFileWriter inputWriter = new CSVFileWriter(inputPath, getController().getAppConfig(), AppUtil.COMMA); try { inputWriter.open(); inputWriter.setColumnNames(templateReader.getColumnNames()); @@ -644,20 +637,25 @@ protected final Map getTestConfig(OperationInfo op, String daoNa protected final Map getTestConfig(OperationInfo op, String daoName, String mappingFile, boolean isExtraction) { Map res = super.getTestConfig(); - res.put(Config.MAPPING_FILE, mappingFile); - res.put(Config.OPERATION, op.name()); - res.put(Config.DAO_NAME, daoName); - res.put(Config.DAO_TYPE, isExtraction ? DataAccessObjectFactory.CSV_WRITE_TYPE + res.put(AppConfig.PROP_MAPPING_FILE, mappingFile); + res.put(AppConfig.PROP_OPERATION, op.name()); + res.put(AppConfig.PROP_DAO_NAME, daoName); + res.put(AppConfig.PROP_DAO_TYPE, isExtraction ? DataAccessObjectFactory.CSV_WRITE_TYPE : DataAccessObjectFactory.CSV_READ_TYPE); - res.put(Config.OUTPUT_STATUS_DIR, getTestStatusDir()); - String apiType = isBulkAPIEnabled(res) ? "Bulk" : "Soap"; - res.put(Config.OUTPUT_SUCCESS, getSuccessFilePath(apiType)); - res.put(Config.OUTPUT_ERROR, getErrorFilePath(apiType)); + res.put(AppConfig.PROP_OUTPUT_STATUS_DIR, getTestStatusDir()); + String apiType = "Soap"; + if (isBulkAPIEnabled(res)) { + apiType = "Bulk"; + } else if (isBulkV2APIEnabled(res)) { + apiType = "BulkV2"; + } + res.put(AppConfig.PROP_OUTPUT_SUCCESS, getSuccessFilePath(apiType)); + res.put(AppConfig.PROP_OUTPUT_ERROR, getErrorFilePath(apiType)); // Don't debug by default, as it slows down the processing if (ProcessTestBase.DEBUG_MESSAGES) { - res.put(Config.DEBUG_MESSAGES, "true"); - res.put(Config.DEBUG_MESSAGES_FILE, + res.put(AppConfig.PROP_DEBUG_MESSAGES, "true"); + res.put(AppConfig.PROP_DEBUG_MESSAGES_FILE, new File(getTestStatusDir(), this.baseName + apiType + "DebugTrace.log").getAbsolutePath()); } @@ -689,7 +687,7 @@ private Controller runProcessWithErrors(Map argMap, int numSucce int numInserts = 0; int numUpdates = 0; - OperationInfo op = OperationInfo.valueOf(argMap.get(Config.OPERATION)); + OperationInfo op = OperationInfo.valueOf(argMap.get(AppConfig.PROP_OPERATION)); if (op == OperationInfo.insert) numInserts = numSuccesses; else if (op != null && op != OperationInfo.upsert) @@ -706,20 +704,43 @@ protected Controller runUpsertProcess(Map args, int numInserts, protected Controller runProcessNegative(Map args, String failureMessage) throws ProcessInitializationException, DataAccessObjectException { - return runProcess(args, false, failureMessage, 0, 0, 0, false); + Controller controller = null; + try { + controller = runProcess(args, false, failureMessage, 0, 0, 0, false); + } catch (RuntimeException ex) { + // ignore + } + return controller; + } + + protected IProcess runBatchProcess(Map argMap) { + if (argMap == null) argMap = getTestConfig(); + argMap.put(AppConfig.PROP_PROCESS_THREAD_NAME, this.baseName); + argMap.put(AppConfig.PROP_READ_ONLY_CONFIG_PROPERTIES, Boolean.TRUE.toString()); + argMap.put(AppConfig.CLI_OPTION_RUN_MODE, AppConfig.RUN_MODE_BATCH_VAL); + + // emulate invocation through process.bat script + String[] args = new String[argMap.size()+1]; + args[0] = getTestConfDir(); + int i = 1; + if (argMap.containsKey(AppConfig.PROP_PROCESS_NAME)) { + args[i++] = argMap.get(AppConfig.PROP_PROCESS_NAME); + argMap.remove(AppConfig.PROP_PROCESS_NAME); + } + for (Map.Entry entry: argMap.entrySet()) + { + args[i++] = entry.getKey() + "=" + entry.getValue(); + } + this.getBinding(); // establish the test connection if not done so already + final NihilistProgressAdapter monitor = new NihilistProgressAdapter(); + return DataLoaderRunner.runApp(args, monitor); } protected Controller runProcess(Map argMap, boolean expectProcessSuccess, String failMessage, int numInserts, int numUpdates, int numFailures, boolean emptyId) throws ProcessInitializationException, DataAccessObjectException { - - if (argMap == null) argMap = getTestConfig(); - - final ProcessRunner runner = ProcessRunner.getInstance(argMap); - runner.setName(this.baseName); - - final TestProgressMontitor monitor = new TestProgressMontitor(); - runner.run(monitor); + IProcess runner = runBatchProcess(argMap); + ILoaderProgress monitor = runner.getMonitor(); Controller controller = runner.getController(); // verify process completed as expected @@ -729,7 +750,8 @@ protected Controller runProcess(Map argMap, boolean expectProces assertTrue("Process failed: " + actualMessage, monitor.isSuccess()); verifyFailureFile(controller, numFailures); //A.S.: To be removed and replaced verifySuccessFile(controller, numInserts, numUpdates, emptyId); - + long serverAPIInvocations = HttpClientTransport.getServerInvocationCount(); + assertTrue("Number of server invocations (" + serverAPIInvocations + ") have exceeded the threshold of " + serverApiInvocationThreshold, serverAPIInvocations <= serverApiInvocationThreshold); } else { assertFalse("Expected process to fail but got success: " + actualMessage, monitor.isSuccess()); } @@ -742,6 +764,10 @@ protected Controller runProcess(Map argMap, boolean expectProces // return the controller used by the process so that the tests can validate success/error output files, etc return controller; } + + protected void setServerApiInvocationThreshold(int threshold) { + serverApiInvocationThreshold = threshold; + } private static final String INSERT_MSG = "Item Created"; private static final Map UPDATE_MSGS; @@ -749,6 +775,7 @@ protected Controller runProcess(Map argMap, boolean expectProces static { UPDATE_MSGS = new EnumMap(OperationInfo.class); UPDATE_MSGS.put(OperationInfo.delete, "Item Deleted"); + UPDATE_MSGS.put(OperationInfo.undelete, "Item Undeleted"); UPDATE_MSGS.put(OperationInfo.hard_delete, "Item Hard Deleted"); UPDATE_MSGS.put(OperationInfo.upsert, "Item Updated"); UPDATE_MSGS.put(OperationInfo.update, "Item Updated"); @@ -759,27 +786,42 @@ protected Controller runProcess(Map argMap, boolean expectProces protected void verifySuccessFile(Controller ctl, int numInserts, int numUpdates, boolean emptyId) throws ParameterLoadException, DataAccessObjectException { - final String successFile = ctl.getConfig().getStringRequired(Config.OUTPUT_SUCCESS); + final String successFile = ctl.getAppConfig().getStringRequired(AppConfig.PROP_OUTPUT_SUCCESS); //final String suceessFule2 = ctl.getConfig(). assertNumRowsInCSVFile(successFile, numInserts + numUpdates); - - Row row = null; - CSVFileReader rdr = new CSVFileReader(successFile, getController()); - String updateMsg = UPDATE_MSGS.get(ctl.getConfig().getOperationInfo()); + boolean isBulkV2Operation = ctl.getAppConfig().isBulkV2APIEnabled(); + + TableRow row = null; + CSVFileReader rdr = new CSVFileReader(new File(successFile), getController().getAppConfig(), true, false); + String expectedUpdateStatusVal = UPDATE_MSGS.get(ctl.getAppConfig().getOperationInfo()); + String expectedInsertStatusVal = INSERT_MSG; + if (isBulkV2Operation && !ctl.getAppConfig().getOperationInfo().isExtraction()) { + expectedInsertStatusVal = "true"; + expectedUpdateStatusVal = "false"; + } int insertsFound = 0; int updatesFound = 0; - while ((row = rdr.readRow()) != null) { - String id = (String)row.get("ID"); + while ((row = rdr.readTableRow()) != null) { + String id = (String)row.get(AppConfig.ID_COLUMN_NAME); + if (id == null) { + id = (String)row.get(AppConfig.ID_COLUMN_NAME_IN_BULKV2); + } if (emptyId) assertEquals("Expected empty id", "", id); else assertValidId(id); - String status = (String)row.get("STATUS"); - if (INSERT_MSG.equals(status)) + String statusValForRow = (String)row.get(AppConfig.STATUS_COLUMN_NAME); + + // status column for Bulk v2 upload operation is different from that for all Bulk v1 operations + // and Bulk v2 extract operation + if (isBulkV2Operation && !ctl.getAppConfig().getOperationInfo().isExtraction()) { + statusValForRow = (String)row.get(AppConfig.STATUS_COLUMN_NAME_IN_BULKV2); + } + if (expectedInsertStatusVal.equals(statusValForRow)) insertsFound++; - else if (updateMsg.equals(status)) + else if (expectedUpdateStatusVal.equals(statusValForRow)) updatesFound++; else - Assert.fail("unrecognized status: " + status); + Assert.fail("unrecognized status: " + statusValForRow); } assertEquals("Wrong number of inserts in success file: " + successFile, numInserts, insertsFound); assertEquals("Wrong number of updates in success file: " + successFile, numUpdates, updatesFound); @@ -866,23 +908,27 @@ protected void verifyAttachmentObjects(Map dbaseFileCorresponden protected void verifyFailureFile(Controller ctl, int numFailures) throws ParameterLoadException, DataAccessObjectException { - assertNumRowsInCSVFile(ctl.getConfig().getStringRequired( - Config.OUTPUT_ERROR), numFailures); + assertNumRowsInCSVFile(ctl.getAppConfig().getStringRequired( + AppConfig.PROP_OUTPUT_ERROR), numFailures); } private void assertNumRowsInCSVFile(String fName, int expectedRows) throws DataAccessObjectException { - CSVFileReader rdr = new CSVFileReader(fName, getController()); + CSVFileReader rdr = new CSVFileReader(new File(fName), getController().getAppConfig(), true, false); rdr.open(); int actualRows = rdr.getTotalRows(); assertEquals("Wrong number of rows in file :" + fName, expectedRows, actualRows); } protected boolean isBulkAPIEnabled(Map argMap) { - return isSettingEnabled(argMap, Config.BULK_API_ENABLED); + return isSettingEnabled(argMap, AppConfig.PROP_BULK_API_ENABLED) + && !isSettingEnabled(argMap, AppConfig.PROP_BULKV2_API_ENABLED); + } + + protected boolean isBulkV2APIEnabled(Map argMap) { + return isSettingEnabled(argMap, AppConfig.PROP_BULKV2_API_ENABLED); } - protected boolean isSettingEnabled(Map argMap, String configKey) { - return Config.TRUE.equalsIgnoreCase(argMap.get(configKey)); + return AppConfig.TRUE.equalsIgnoreCase(argMap.get(configKey)); } protected Map getUpdateTestConfig(boolean isUpsert, String extIdField, int numAccountsToInsert) @@ -915,8 +961,103 @@ protected Map getUpdateTestConfig(String fileNameBase, boolean i final File mappingFile = new File(getTestDataDir(), fileNameBase + "Map.sdl"); final Map argMap = getTestConfig(isUpsert ? OperationInfo.upsert : OperationInfo.update, updateFileName, mappingFile.getAbsolutePath(), false); - if (hasExtId) argMap.put(Config.EXTERNAL_ID_FIELD, extIdField); + if (hasExtId) argMap.put(AppConfig.PROP_IDLOOKUP_FIELD, extIdField); return argMap; } + protected Map getTestConfig() { + Map configArgsMap = super.getTestConfig(); + // run process tests in batch mode + configArgsMap.put(AppConfig.CLI_OPTION_RUN_MODE, AppConfig.RUN_MODE_BATCH_VAL); + return configArgsMap; + } + + + @SuppressWarnings("unchecked") + protected UpsertResult[] doUpsert(String entity, Map sforceMapping) throws Exception { + // now convert to a dynabean array for the client + // setup our dynabeans + BasicDynaClass dynaClass = setupDynaClass(entity, (Collection)(Collection)(sforceMapping.values())); + + DynaBean sforceObj = dynaClass.newInstance(); + + // This does an automatic conversion of types. + BeanUtils.copyProperties(sforceObj, sforceMapping); + + List beanList = new ArrayList(); + beanList.add(sforceObj); + + // get the client and make the insert call + PartnerClient client = new PartnerClient(getController()); + UpsertResult[] results = client.loadUpserts(beanList); + for (UpsertResult result : results) { + if (!result.getSuccess()) { + Assert.fail("Upsert returned an error: " + result.getErrors()[0].getMessage()); + } + } + return results; + } + + /** + * Make sure to set external id field + */ + protected String setExtIdField(String extIdField) { + getController().getAppConfig().setValue(AppConfig.PROP_IDLOOKUP_FIELD, extIdField); + return extIdField; + } + + /** + * Get a random account external id for upsert testing + * + * @param entity + * TODO + * @param whereClause + * TODO + * @param prevValue + * Indicate that the value should be different from the specified + * value or null if uniqueness not required + * @return String Account external id value + */ + protected Object getRandomExtId(String entity, String whereClause, Object prevValue) throws ConnectionException { + + // insert couple of accounts so there're at least two records to work with + upsertSfdcRecords(entity, 2); + + // get the client and make the query call + String extIdField = getController().getAppConfig().getString(AppConfig.PROP_IDLOOKUP_FIELD); + PartnerClient client = new PartnerClient(getController()); + // only get the records that have external id set, avoid nulls + String soql = "select " + extIdField + " from " + entity + " where " + whereClause + " and " + extIdField + + " != null"; + if (prevValue != null) { + soql += " and " + + extIdField + + "!= " + + (prevValue.getClass().equals(String.class) ? ("'" + prevValue + "'") : String + .valueOf(prevValue)); + } + QueryResult result = client.query(soql); + SObject[] records = result.getRecords(); + assertNotNull("Operation should return non-null values", records); + assertTrue("Operation should return 1 or more records", records.length > 0); + assertNotNull("Records should have non-null field: " + extIdField + " values", records[0] + .getField(extIdField)); + + return records[0].getField(extIdField); + } + + protected BasicDynaClass setupDynaClass(String entity, Collection sfFields) throws ConnectionException { + getController().getAppConfig().setValue(AppConfig.PROP_ENTITY, entity); + PartnerClient client = getController().getPartnerClient(); + if (!client.isLoggedIn()) { + client.connect(); + } + + getController().setFieldTypes(); + getController().setReferenceDescribes(sfFields); + DynaProperty[] dynaProps = SforceDynaBean.createDynaProps(getController().getPartnerClient().getFieldTypes(), getController()); + BasicDynaClass dynaClass = SforceDynaBean.getDynaBeanInstance(dynaProps); + SforceDynaBean.registerConverters(getController().getAppConfig()); + return dynaClass; + } } diff --git a/src/test/java/com/salesforce/dataloader/ui/OAuthSecretFlowTests.java b/src/test/java/com/salesforce/dataloader/ui/OAuthSecretFlowTests.java deleted file mode 100644 index b21ea77cf..000000000 --- a/src/test/java/com/salesforce/dataloader/ui/OAuthSecretFlowTests.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.ui; - -import com.google.gson.FieldNamingPolicy; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; -import com.salesforce.dataloader.ConfigTestBase; -import com.salesforce.dataloader.client.SimplePost; -import com.salesforce.dataloader.client.SimplePostFactory; -import com.salesforce.dataloader.config.Config; -import com.salesforce.dataloader.exception.ParameterLoadException; -import com.salesforce.dataloader.model.OAuthToken; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import static org.mockito.Mockito.*; - -import java.io.*; -import java.net.URISyntaxException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.function.Function; - -/** - * Created by rmazzeo on 12/9/15. - */ -public class OAuthSecretFlowTests extends ConfigTestBase { - - private SimplePost mockSimplePost; - private Config config; - private ArrayList existingOAuthEnvironments; - private String oauthServer; - private String oauthClientId; - private String oauthRedirectUri; - private String existingEndPoint; - private Function existingConstructor; - - @Before - public void testSetup(){ - config = getController().getConfig(); - existingOAuthEnvironments = config.getStrings(Config.OAUTH_ENVIRONMENTS); - existingEndPoint = config.getString(Config.ENDPOINT); - oauthServer = "http://OAUTH_PARTIAL_SERVER"; - oauthClientId = "CLIENTID"; - oauthRedirectUri = "REDIRECTURI"; - mockSimplePost = mock(SimplePost.class); - - config.setValue(Config.OAUTH_ENVIRONMENTS, "Testing"); - config.setOAuthEnvironmentString("Testing", Config.OAUTH_PARTIAL_SERVER, oauthServer); - config.setOAuthEnvironmentString("Testing", Config.OAUTH_PARTIAL_CLIENTID, oauthClientId); - config.setOAuthEnvironmentString("Testing", Config.OAUTH_PARTIAL_REDIRECTURI, oauthRedirectUri); - config.setOAuthEnvironment("Testing"); - - existingConstructor = SimplePostFactory.getConstructor(); - SimplePostFactory.setConstructor(c -> mockSimplePost); - } - - @After - public void testCleanup(){ - config.setValue(Config.OAUTH_ENVIRONMENTS, existingOAuthEnvironments.toArray(new String[0])); - config.setValue(Config.ENDPOINT, existingEndPoint); - SimplePostFactory.setConstructor(existingConstructor); - } - - @Test - public void testGetStartUrl(){ - try { - String expected = "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?response_type=code&display=popup&client_id=CLIENTID&redirect_uri=REDIRECTURI"; - String actual = OAuthSecretFlow.getStartUrlImpl(config); - - Assert.assertEquals( "OAuth Token Flow returned the wrong url", expected, actual); - } catch (UnsupportedEncodingException e) { - Assert.fail("could not get start url" + e.toString()); - } - } - - @Test - public void testInvalidInitialReponseUrl(){ - try { - String expected = null; - String actual = OAuthSecretFlow.OAuthSecretBrowserListener.handleInitialUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?doit=1"); - Assert.assertEquals("OAuthToken should not have handled this", expected, actual); - - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } - - @Test - public void testValidInitialResponseUrl(){ - try { - String expected = "TOKEN"; - String actual = OAuthSecretFlow.OAuthSecretBrowserListener.handleInitialUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?code=TOKEN"); - Assert.assertEquals("OAuthToken should not have handled this", expected, actual); - - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } - - @Test - public void testValidSecondResponseAccessToken(){ - try { - - Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - OAuthToken token = new OAuthToken(); - token.setAccessToken("ACCESS"); - String jsonToken = gson.toJson(token); - InputStream input = new ByteArrayInputStream(jsonToken.getBytes(StandardCharsets.UTF_8)); - when(mockSimplePost.getInput()).thenAnswer(i -> input); - when(mockSimplePost.isSuccessful()).thenReturn(true); - - SimplePost simplePost = OAuthSecretFlow.OAuthSecretBrowserListener.handleSecondPost("simplePost", config); - - String expected = "ACCESS"; - String actual = config.getString(Config.OAUTH_ACCESSTOKEN); - when(mockSimplePost.isSuccessful()).thenReturn(true); - - Assert.assertEquals("Access token was not set", expected, actual); - - } catch (ParameterLoadException | IOException e) { - Assert.fail("Could not handle second request:" + e.toString()); - } - } - - @Test - public void testValidSecondResponseRefreshToken(){ - try { - - Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - OAuthToken token = new OAuthToken(); - token.setRefreshToken("REFRESHTOKEN"); - String jsonToken = gson.toJson(token); - InputStream input = new ByteArrayInputStream(jsonToken.getBytes(StandardCharsets.UTF_8)); - when(mockSimplePost.getInput()).thenAnswer(i -> input); - when(mockSimplePost.isSuccessful()).thenReturn(true); - - SimplePost simplePost = OAuthSecretFlow.OAuthSecretBrowserListener.handleSecondPost("simplePost", config); - - String expected = "REFRESHTOKEN"; - String actual = config.getString(Config.OAUTH_REFRESHTOKEN); - - Assert.assertEquals("Access token was not set", expected, actual); - - } catch (ParameterLoadException | IOException e) { - Assert.fail("Could not handle second request:" + e.toString()); - } - } - - @Test - public void testValidSecondResponseInstanceUrl(){ - try { - - Gson gson = new GsonBuilder() - .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) - .create(); - OAuthToken token = new OAuthToken(); - token.setInstanceUrl("http://INSTANCEURL"); - String jsonToken = gson.toJson(token); - InputStream input = new ByteArrayInputStream(jsonToken.getBytes(StandardCharsets.UTF_8)); - when(mockSimplePost.getInput()).thenAnswer(i -> input); - when(mockSimplePost.isSuccessful()).thenReturn(true); - - SimplePost simplePost = OAuthSecretFlow.OAuthSecretBrowserListener.handleSecondPost("simplePost", config); - - String expected = "http://INSTANCEURL"; - String actual = config.getString(Config.ENDPOINT); - - Assert.assertEquals("Access token was not set", expected, actual); - - } catch (ParameterLoadException | IOException e) { - Assert.fail("Could not handle second request:" + e.toString()); - } - } -} diff --git a/src/test/java/com/salesforce/dataloader/ui/OAuthTokenFlowTests.java b/src/test/java/com/salesforce/dataloader/ui/OAuthTokenFlowTests.java deleted file mode 100644 index 867fc3fd7..000000000 --- a/src/test/java/com/salesforce/dataloader/ui/OAuthTokenFlowTests.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2015, salesforce.com, inc. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are permitted provided - * that the following conditions are met: - * - * Redistributions of source code must retain the above copyright notice, this list of conditions and the - * following disclaimer. - * - * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and - * the following disclaimer in the documentation and/or other materials provided with the distribution. - * - * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or - * promote products derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A - * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR - * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE - * POSSIBILITY OF SUCH DAMAGE. - */ -package com.salesforce.dataloader.ui; - -import com.salesforce.dataloader.ConfigTestBase; -import com.salesforce.dataloader.config.Config; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; -import java.util.ArrayList; - -/** - * test the oauth token flow as best we can. sadly there is SWT here which isn't very testable for unit tests - */ -public class OAuthTokenFlowTests extends ConfigTestBase { - - private Config config; - private ArrayList existingOAuthEnvironments; - private String oauthServer; - private String oauthClientId; - private String oauthRedirectUri; - private String existingEndPoint; - - @Before - public void testSetup(){ - config = getController().getConfig(); - existingOAuthEnvironments = config.getStrings(Config.OAUTH_ENVIRONMENTS); - existingEndPoint = config.getString(Config.ENDPOINT); - oauthServer = "http://OAUTH_PARTIAL_SERVER"; - oauthClientId = "CLIENTID"; - oauthRedirectUri = "REDIRECTURI"; - - config.setValue(Config.OAUTH_ENVIRONMENTS, "Testing"); - config.setOAuthEnvironmentString("Testing", Config.OAUTH_PARTIAL_SERVER, oauthServer); - config.setOAuthEnvironmentString("Testing", Config.OAUTH_PARTIAL_CLIENTID, oauthClientId); - config.setOAuthEnvironmentString("Testing", Config.OAUTH_PARTIAL_REDIRECTURI, oauthRedirectUri); - config.setOAuthEnvironment("Testing"); - } - - @After - public void testCleanup(){ - config.setValue(Config.OAUTH_ENVIRONMENTS, existingOAuthEnvironments.toArray(new String[0])); - config.setValue(Config.ENDPOINT, existingEndPoint); - } - - @Test - public void testGetStartUrl(){ - try { - String expected = "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?response_type=token&display=popup&client_id=CLIENTID&redirect_uri=REDIRECTURI"; - String actual = OAuthTokenFlow.getStartUrlImpl(config); - - Assert.assertEquals( "OAuth Token Flow returned the wrong url", expected, actual); - } catch (UnsupportedEncodingException e) { - Assert.fail("could not get start url" + e.toString()); - } - } - - @Test - public void testInvalidReponseUrl(){ - try { - Boolean condition = OAuthTokenFlow.OAuthTokenBrowserLister.handleCompletedUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize?doit=1", config); - Assert.assertFalse("OAuthToken should not have handled this", condition); - - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } - - @Test - public void testValidResponseUrl(){ - try { - Boolean condition = OAuthTokenFlow.OAuthTokenBrowserLister.handleCompletedUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN", config); - Assert.assertTrue("OAuthToken should have handled this", condition); - - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } - - @Test - public void testValidResponseUrlSetsAccessToken(){ - try { - OAuthTokenFlow.OAuthTokenBrowserLister.handleCompletedUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN", config); - String expected = "TOKEN"; - String actual = config.getString(Config.OAUTH_ACCESSTOKEN); - - Assert.assertEquals("Incorrect access token found in config", expected, actual); - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } - - @Test - public void testValidResponseUrlSetsRefreshToken(){ - try { - OAuthTokenFlow.OAuthTokenBrowserLister.handleCompletedUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN&refresh_token=REFRESHTOKEN", config); - String expected = "REFRESHTOKEN"; - String actual = config.getString(Config.OAUTH_REFRESHTOKEN); - - Assert.assertEquals("Incorrect refresh token found in config", expected, actual); - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } - - @Test - public void testValidResponseUrlSetsEndPoint(){ - try { - OAuthTokenFlow.OAuthTokenBrowserLister.handleCompletedUrl( "http://OAUTH_PARTIAL_SERVER/services/oauth2/authorize#access_token=TOKEN&instance_url=INSTANCEURL", config); - String expected = "INSTANCEURL"; - String actual = config.getString(Config.ENDPOINT); - - Assert.assertEquals("Incorrect refresh token found in config", expected, actual); - } catch (URISyntaxException e) { - Assert.fail("Could not handle the url:" + e.toString()); - } - } -} diff --git a/src/test/java/com/salesforce/dataloader/util/AccountRowComparator.java b/src/test/java/com/salesforce/dataloader/util/AccountRowComparator.java index fd9099a47..4998fd1a8 100644 --- a/src/test/java/com/salesforce/dataloader/util/AccountRowComparator.java +++ b/src/test/java/com/salesforce/dataloader/util/AccountRowComparator.java @@ -25,12 +25,12 @@ */ package com.salesforce.dataloader.util; -import com.salesforce.dataloader.model.Row; +import com.salesforce.dataloader.ConfigTestBase; +import com.salesforce.dataloader.model.TableRow; import static com.salesforce.dataloader.dao.database.DatabaseTestUtil.NAME_COL; import java.util.Comparator; -import java.util.Map; /** * Comparator for the account rows from the database reader. @@ -38,9 +38,9 @@ * @author Alex Warshavsky * @since 8.0 */ -public class AccountRowComparator implements Comparator { +public class AccountRowComparator extends ConfigTestBase implements Comparator { - private static String getName(Row o1) { + private static String getName(TableRow o1) { return o1.get(NAME_COL).toString(); } @@ -58,7 +58,7 @@ public AccountRowComparator(boolean isReverse) { } @Override - public int compare(Row o1, Row o2) { + public int compare(TableRow o1, TableRow o2) { final int result = getName(o1).compareTo(getName(o2)); return isReverse ? -result : result; } diff --git a/src/test/java/com/salesforce/dataloader/util/AppUtilTest.java b/src/test/java/com/salesforce/dataloader/util/AppUtilTest.java new file mode 100644 index 000000000..deaa00ae5 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/util/AppUtilTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.util; + +import org.junit.Assert; +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; + +public class AppUtilTest extends ConfigTestBase { + @Test + public void testHttpsTester() { + Assert.assertTrue(AppUtil.isValidHttpsUrl("https://my.com")); + Assert.assertFalse(AppUtil.isValidHttpsUrl("http://my.com")); + Assert.assertFalse(AppUtil.isValidHttpsUrl("my.com")); + Assert.assertFalse(AppUtil.isValidHttpsUrl("ftp://my.com")); + } +} diff --git a/src/test/java/com/salesforce/dataloader/util/Base64.java b/src/test/java/com/salesforce/dataloader/util/Base64.java index 1104d900b..8ff04154b 100644 --- a/src/test/java/com/salesforce/dataloader/util/Base64.java +++ b/src/test/java/com/salesforce/dataloader/util/Base64.java @@ -174,7 +174,9 @@ package com.salesforce.dataloader.util; -public class Base64 +import com.salesforce.dataloader.ConfigTestBase; + +public class Base64 extends ConfigTestBase { /* ******** P U B L I C F I E L D S ******** */ @@ -1386,7 +1388,7 @@ public static Object decodeToObject( @Override public Class resolveClass(java.io.ObjectStreamClass streamClass) throws java.io.IOException, ClassNotFoundException { - Class c = Class.forName(streamClass.getName(), false, loader); + Class c = Class.forName(streamClass.getName(), false, loader); if( c == null ){ return super.resolveClass(streamClass); } else { diff --git a/src/test/java/com/salesforce/dataloader/util/DAORowUtilTest.java b/src/test/java/com/salesforce/dataloader/util/DAORowUtilTest.java new file mode 100644 index 000000000..fed27f761 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/util/DAORowUtilTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; + +import static org.junit.Assert.assertEquals; + +public class DAORowUtilTest extends ConfigTestBase { + @Test + public void testNullPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue(null, null); + assertEquals("incorrect conversion: ", + null, + result); + } + @Test + public void testTenDigitUSPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue("1234567890", "en_US"); + assertEquals("incorrect conversion: ", + "(123) 456-7890", + result); + } + @Test + public void testTenDigitCAPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue("1234567890", "en_CA"); + assertEquals("incorrect conversion: ", + "(123) 456-7890", + result); + } + @Test + public void testElevenDigitUSPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue("11234567890", "en_US"); + assertEquals("incorrect conversion: ", + "(123) 456-7890", + result); + } + @Test + public void testElevenDigitCAPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue("11234567890", "en_CA"); + assertEquals("incorrect conversion: ", + "(123) 456-7890", + result); + } + @Test + public void testTwelveDigitPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue("112345678901", "en_US"); + assertEquals("incorrect conversion: ", + "112345678901", + result); + } + @Test + public void testNineDigitPhoneFieldValue() { + String result = DAORowUtil.getPhoneFieldValue("123456789", "en_US"); + assertEquals("incorrect conversion: ", + "123456789", + result); + } + @Test + public void testElevenDigitPhoneFieldValueNotStartingWithOne() { + String result = DAORowUtil.getPhoneFieldValue("21234567890", "en_US"); + assertEquals("incorrect conversion: ", + "21234567890", + result); + } + @Test + public void testTenDigitPhoneFieldValueStartingWithPlus() { + String result = DAORowUtil.getPhoneFieldValue("+1234567890", "en_US"); + assertEquals("incorrect conversion: ", + "+1234567890", + result); + } + @Test + public void testTenDigitPhoneFieldValueStartingWithMinus() { + String result = DAORowUtil.getPhoneFieldValue("-1234567890", "en_US"); + assertEquals("incorrect conversion: ", + "-1234567890", + result); + } + @Test + public void testTenDigitPhoneFieldValueNonUSLocale() { + String result = DAORowUtil.getPhoneFieldValue("1234567890", "en_UK"); + assertEquals("incorrect conversion: ", + "1234567890", + result); + } +} diff --git a/src/test/java/com/salesforce/dataloader/util/LoadRateCalculatorTest.java b/src/test/java/com/salesforce/dataloader/util/LoadRateCalculatorTest.java new file mode 100644 index 000000000..662f8b864 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/util/LoadRateCalculatorTest.java @@ -0,0 +1,149 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package com.salesforce.dataloader.util; + +import org.junit.Test; + +import com.salesforce.dataloader.ConfigTestBase; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + + +public class LoadRateCalculatorTest extends ConfigTestBase { + @Test + public void testRateCalculatorZeroCompletion() { + LoadRateCalculator rateCalculator = new LoadRateCalculator(); + rateCalculator.start(3600); + String message = rateCalculator.calculateSubTask(0, 0); + assertEquals("incorrect rate calculation: ", + "Processed 0 of 3,600 records with 0 successes and 0 errors.", + message); + + } + @Test + public void testRateCalculatorFirstBatchCompletion() { + LoadRateCalculator rateCalculator = new LoadRateCalculator(); + rateCalculator.start(3600); + String message = rateCalculator.calculateSubTask(0, 0); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(1, 0); + boolean success = message.contains("1 successes and 0 errors") + && message.contains("Rate: 3,600 records per hour"); + assertTrue("incorrect rate calculation: ", success); + } + + @Test + public void testRateCalculatorMultiBatchCompletion() { + LoadRateCalculator rateCalculator = new LoadRateCalculator(); + rateCalculator.start(3600); + String message = rateCalculator.calculateSubTask(0, 0); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(1, 0); + rateCalculator.start(3600); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(2, 1); + boolean success = message.contains("Processed 2 of 3,600 records in 0 minutes, 2 seconds with 1 successes and 1 errors. \nRate: 3,600 records per hour."); + assertTrue("incorrect rate calculation - " + message, success); + + rateCalculator.start(3600); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(3, 1); + success = message.contains("Processed 3 of 3,600 records") + && message.contains("0 minutes, 3 seconds") + && message.contains("2 successes and 1 errors") + && message.contains("Rate: 3,600 records per hour"); + assertTrue("incorrect rate calculation - " + message, success); + + rateCalculator.start(3600); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(4, 1); + success = message.contains("Processed 4 of 3,600 records") + && message.contains("0 minutes, 4 seconds") + && message.contains("3 successes and 1 errors") + && message.contains("Rate: 3,600 records per hour"); + assertTrue("incorrect rate calculation - " + message, success); + + rateCalculator.start(3600); + try { + Thread.sleep(63000); // sleep for a minute + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(7, 2); + success = message.contains("Processed 7 of 3,600 records in 1 minutes, 7 seconds with 5 successes and 2 errors. \nRate: 376 records per hour."); + success = message.contains("Processed 7 of 3,600 records") + && message.contains("1 minutes, 7 seconds") + && message.contains("5 successes and 2 errors") + && message.contains("Rate: 376 records per hour"); + assertTrue("incorrect rate calculation - " + message, success); + } + + @Test + public void testRateCalculatorLongTimeForCompletion() { + LoadRateCalculator rateCalculator = new LoadRateCalculator(); + rateCalculator.start(999999999); + String message = rateCalculator.calculateSubTask(0, 0); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + message = rateCalculator.calculateSubTask(1, 0); + assertEquals("incorrect rate calculation: ", + "Processed 1 of 999,999,999 records with 1 successes and 0 errors.", + message); + } + +} diff --git a/src/test/java/com/salesforce/dataloader/util/TestRecordsUtil.java b/src/test/java/com/salesforce/dataloader/util/TestRecordsUtil.java index 49997cfd0..c3f51d978 100644 --- a/src/test/java/com/salesforce/dataloader/util/TestRecordsUtil.java +++ b/src/test/java/com/salesforce/dataloader/util/TestRecordsUtil.java @@ -25,7 +25,7 @@ */ package com.salesforce.dataloader.util; -import com.salesforce.dataloader.TestBase; +import com.salesforce.dataloader.ConfigTestBase; import com.salesforce.dataloader.process.ProcessTestBase; import java.util.Arrays; @@ -69,7 +69,7 @@ private void execute(String[] args) throws Exception { int numRecords = argMap.containsKey("-n") ? Integer.parseInt(argMap.get("-n")) : 100; String whereClause = argMap.get("-q"); if(whereClause == null) { - whereClause = TestBase.ACCOUNT_WHERE_CLAUSE; + whereClause = ConfigTestBase.ACCOUNT_WHERE_CLAUSE; } setupController(); @@ -78,7 +78,7 @@ private void execute(String[] args) throws Exception { } else if("upsert".equals(operation)) { upsertSfdcAccounts(numRecords); } else if("delete".equals(operation)) { - deleteSfdcRecords("Account",whereClause, 0); + this.getBinding().deleteSfdcRecords("Account",whereClause, 0); } } diff --git a/src/test/resources/itest.xml b/src/test/resources/itest.xml deleted file mode 100644 index f5468abff..000000000 --- a/src/test/resources/itest.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/test/resources/test.properties b/src/test/resources/test.properties index 465917a1b..d894aaa14 100644 --- a/src/test/resources/test.properties +++ b/src/test/resources/test.properties @@ -18,10 +18,11 @@ test.password=${test.password} test.log.level=debug ## these properties are needed to configure dataloader tests -testfiles.dir=${testfiles.dir} +testfiles.dir=${test.testfiles.directory} test.src.dir=${project.build.testSourceDirectory} main.src.dir=${project.build.sourceDirectory} target.dir=${project.build.directory} +process.encryptionKeyFile=${test.encryptionFile} ## TODO: properties below here don't really belong and should be removed ## defaults that don't necessarily belong here diff --git a/src/test/resources/test.properties~ b/src/test/resources/test.properties~ deleted file mode 100644 index 05076733c..000000000 --- a/src/test/resources/test.properties~ +++ /dev/null @@ -1,27 +0,0 @@ -## DO NOT CHECK IN USERNAMES, PASSWORDS OR INTERNAL ENDPOINTS ON THIS FILE -## -## For CI, these properties are configured externally in a Jenkins configuration - -## endpoint -test.endpoint=https://na1-blitz02.soma.salesforce.com -test.redirect=true - -## user/org/passwd -test.org=pint.com -test.user.default=admin@pint.com -test.user.restricted=admin@pint.com -test.password=test1234L - -## test logging level -test.log.level=debug - -## these properties are needed to configure dataloader tests -testfiles.dir=${testfiles.dir} -test.src.dir=${project.build.testSourceDirectory} -main.src.dir=${project.build.sourceDirectory} -target.dir=${project.build.directory} - -## TODO: properties below here don't really belong and should be removed -## defaults that don't necessarily belong here -test.account.extid=Oracle_Id__c -test.entity.default=Account diff --git a/src/test/resources/testfiles/conf/config.properties b/src/test/resources/testfiles/conf/config.properties index d2663a36c..d55164ca3 100644 --- a/src/test/resources/testfiles/conf/config.properties +++ b/src/test/resources/testfiles/conf/config.properties @@ -1,6 +1,6 @@ ## config.properties used by dataloader tests -sfdc.endpoint=${test.endpoint} +sfdc.endpoint.Production=${test.endpoint} sfdc.resetUrlOnLogin=${test.redirect} sfdc.username=${test.user.default} sfdc.password=${test.password} @@ -15,3 +15,4 @@ sfdc.bulkApiCheckStatusInterval=500 # default setting for test loader.csvComma=true loader.csvTab=true +sfdc.oauth.loginfrombrowser=false \ No newline at end of file diff --git a/src/test/resources/testfiles/conf/database-conf.xml b/src/test/resources/testfiles/conf/database-conf.xml index 99e078355..5bb702e9e 100644 --- a/src/test/resources/testfiles/conf/database-conf.xml +++ b/src/test/resources/testfiles/conf/database-conf.xml @@ -1,51 +1,52 @@ - + + class="org.apache.commons.dbcp2.BasicDataSource" + destroy-method="close" scope="singleton"> - + + scope="singleton"> + scope="singleton"> + scope="singleton"> + scope="singleton"> + scope="singleton"> + scope="singleton"> + class="com.salesforce.dataloader.dao.database.SqlConfig" scope="singleton"> SELECT ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER @@ -66,7 +67,7 @@ + class="com.salesforce.dataloader.dao.database.SqlConfig" scope="singleton"> SELECT ACCOUNT_NAME, BUSINESS_PHONE, SFDC_ACCOUNT_ID, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER @@ -92,7 +93,7 @@ + class="com.salesforce.dataloader.dao.database.SqlConfig" scope="singleton"> SELECT ACCOUNT_NAME, BUSINESS_PHONE, ACCOUNT_EXT_ID, ANNUAL_REVENUE, LAST_UPDATED, ACCOUNT_NUMBER @@ -117,7 +118,7 @@ + class="com.salesforce.dataloader.dao.database.SqlConfig" scope="singleton"> update DATALOADER accounts @@ -144,7 +145,7 @@ + class="com.salesforce.dataloader.dao.database.SqlConfig" scope="singleton"> INSERT INTO DATALOADER ( @@ -165,7 +166,7 @@ + class="com.salesforce.dataloader.dao.database.SqlConfig" scope="singleton"> DELETE FROM DATALOADER diff --git a/src/test/resources/testfiles/conf/log-conf.xml b/src/test/resources/testfiles/conf/log-conf.xml deleted file mode 100644 index 778358f8d..000000000 --- a/src/test/resources/testfiles/conf/log-conf.xml +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/test/resources/testfiles/conf/log4j2.properties b/src/test/resources/testfiles/conf/log4j2.properties new file mode 100644 index 000000000..74e5deade --- /dev/null +++ b/src/test/resources/testfiles/conf/log4j2.properties @@ -0,0 +1,26 @@ +name = ${project.name} Tests +monitorInterval = 5 +property.basePath = ${sys:java.io.tmpdir} +# RollingFileAppender name, pattern, path and rollover policy +appender.rolling.type = RollingFile +appender.rolling.name = fileAppender +appender.rolling.fileName= ${basePath}/sdl.log +appender.rolling.filePattern= ${basePath}/sdl-%d{yyyy-MM-dd}.log +appender.rolling.layout.type = PatternLayout +appender.rolling.layout.pattern = %d %-5p [%t] %C{2} %M (%F:%L) - %m%n +appender.rolling.policies.type = Policies + +# CONSOLE Appender +appender.console.type = Console +appender.console.name = STDOUT +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %d %-5p [%t] %C{2} %M (%F:%L) - %m%n + +# RollingFileAppender rotation policy +appender.rolling.policies.size.type = SizeBasedTriggeringPolicy +appender.rolling.policies.size.size = 100KB + +# Configure root logger +rootLogger.level = info +rootLogger.appenderRef.rolling.ref = fileAppender +rootLogger.appenderRef.stdout.ref = STDOUT \ No newline at end of file diff --git a/src/test/resources/testfiles/conf/process-conf.xml b/src/test/resources/testfiles/conf/process-conf.xml index cde97488e..a555cd1f5 100644 --- a/src/test/resources/testfiles/conf/process-conf.xml +++ b/src/test/resources/testfiles/conf/process-conf.xml @@ -1,19 +1,20 @@ - + + scope="prototype"> maximumBatchRowsDb job gets the Account updates from database and uploads them to salesforce using 'upsert'. - - - + + + - + @@ -23,22 +24,19 @@ + scope="prototype"> upsertAccountDb job gets the Account updates from database and uploads them to salesforce using 'upsert'. - - - - - + + + - - + @@ -48,19 +46,19 @@ + scope="prototype"> extractAccountDb job gets account info from salesforce and updates or inserts info into database." - - - + + + - + @@ -70,53 +68,53 @@ + scope="prototype"> extractAccountCsv job gets account info from salesforce and saves info into a CSV file." - - - + + + - + - + + scope="prototype"> updateAccountCsv job gets account info to salesforce and saves from a CSV file." - - - + + + - - + + + scope="prototype"> insertNullsDBProcess job gets the Account updates from database and uploads them to salesforce using 'upsert'. - - - + + + - + diff --git a/src/test/resources/testfiles/data/accountsForInsert.csv b/src/test/resources/testfiles/data/accountsForInsert.csv index ee90fb101..d3bdc6698 100644 --- a/src/test/resources/testfiles/data/accountsForInsert.csv +++ b/src/test/resources/testfiles/data/accountsForInsert.csv @@ -1,5 +1,5 @@ -"Name","NumEmp","Industry" -"Company A","25","Heavy Industry" -"Company B","36","Mining" -"Company C","47","Aerospace" -"Company D","58","Fiber Optics" +"Name","NumEmp","Industry","RichText" +"Company A","25","Heavy Industry"," 4 leading spaces and 2 trailing

    spaces

    " +"Company B","36","Mining","

    4 leading spaces and 2 trailing spaces

    " +"Company C","47","Aerospace"," 4 leading spaces and 2 trailing spaces with 3 spaces after < char " +"Company D","58","Fiber Optics"," 4 leading spaces and 2 trailing spaces " diff --git a/src/test/resources/testfiles/data/acctsWithBinaryDataInRTF.csv b/src/test/resources/testfiles/data/acctsWithBinaryDataInRTF.csv new file mode 100644 index 000000000..9cea75006 --- /dev/null +++ b/src/test/resources/testfiles/data/acctsWithBinaryDataInRTF.csv @@ -0,0 +1,2 @@ +"Name","RICHTEXT__c" +"Company A","" \ No newline at end of file diff --git a/src/test/resources/testfiles/data/binaryDataInRTFQueryResultMap.sdl b/src/test/resources/testfiles/data/binaryDataInRTFQueryResultMap.sdl new file mode 100644 index 000000000..bffe76211 --- /dev/null +++ b/src/test/resources/testfiles/data/binaryDataInRTFQueryResultMap.sdl @@ -0,0 +1,2 @@ +Name=Name +RICHTEXT__c=RICHTEXT__c \ No newline at end of file diff --git a/src/test/resources/testfiles/data/csvtext_BOM_UTF16BE.csv b/src/test/resources/testfiles/data/csvtext_BOM_UTF16BE.csv new file mode 100644 index 000000000..47c6084f1 Binary files /dev/null and b/src/test/resources/testfiles/data/csvtext_BOM_UTF16BE.csv differ diff --git a/src/test/resources/testfiles/data/csvtext_BOM_UTF16LE.csv b/src/test/resources/testfiles/data/csvtext_BOM_UTF16LE.csv new file mode 100644 index 000000000..40e1f6eec Binary files /dev/null and b/src/test/resources/testfiles/data/csvtext_BOM_UTF16LE.csv differ diff --git a/src/test/resources/testfiles/data/csvtext_BOM_UTF32BE.csv b/src/test/resources/testfiles/data/csvtext_BOM_UTF32BE.csv new file mode 100644 index 000000000..528248683 Binary files /dev/null and b/src/test/resources/testfiles/data/csvtext_BOM_UTF32BE.csv differ diff --git a/src/test/resources/testfiles/data/csvtext_BOM_UTF32LE.csv b/src/test/resources/testfiles/data/csvtext_BOM_UTF32LE.csv new file mode 100644 index 000000000..62b4e585e Binary files /dev/null and b/src/test/resources/testfiles/data/csvtext_BOM_UTF32LE.csv differ diff --git a/src/test/resources/testfiles/data/csvtext_BOM_UTF8.csv b/src/test/resources/testfiles/data/csvtext_BOM_UTF8.csv new file mode 100644 index 000000000..a8c7d407e --- /dev/null +++ b/src/test/resources/testfiles/data/csvtext_BOM_UTF8.csv @@ -0,0 +1,3 @@ +"column1","column2","column3" +row1-1,row1-2,row1-3 +row2-1,row2-2,row2-3 diff --git a/src/test/resources/testfiles/data/dateEndingInZ.csv b/src/test/resources/testfiles/data/dateEndingInZ.csv index 4aac89063..cbe2f827f 100644 --- a/src/test/resources/testfiles/data/dateEndingInZ.csv +++ b/src/test/resources/testfiles/data/dateEndingInZ.csv @@ -1,2 +1,2 @@ -NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE -account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T12:00:00.000Z \ No newline at end of file +NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE,DATE_ONLY +account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T12:00:00.000Z,2010-10-14T12:00:00.000Z diff --git a/src/test/resources/testfiles/data/dateEndingInZMap.sdl b/src/test/resources/testfiles/data/dateEndingInZMap.sdl index ace7819f5..1eae31d78 100644 --- a/src/test/resources/testfiles/data/dateEndingInZMap.sdl +++ b/src/test/resources/testfiles/data/dateEndingInZMap.sdl @@ -7,4 +7,5 @@ TYPE=Type ANNUALREVENUE=AnnualRevenue PHONE=Phone ORACLE_ID__C=Oracle_Id__c -DATE=CustomDateTime__c \ No newline at end of file +DATE=CustomDateTime__c +DATE_ONLY=CustomDateOnly__c diff --git a/src/test/resources/testfiles/data/dateOnlyWithTimeZone.csv b/src/test/resources/testfiles/data/dateOnlyWithTimeZone.csv new file mode 100644 index 000000000..fff3b911e --- /dev/null +++ b/src/test/resources/testfiles/data/dateOnlyWithTimeZone.csv @@ -0,0 +1,3 @@ +NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE_ONLY +account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T00:00:00.000Z +account insert #1,Account,415-555-0000,ACCT_1,http://www.accountInsert1.com,0,1-00001,2010-10-14T00:00:00.000 diff --git a/src/test/resources/testfiles/data/dateOnlyWithTimeZoneMap.sdl b/src/test/resources/testfiles/data/dateOnlyWithTimeZoneMap.sdl new file mode 100644 index 000000000..86f15c342 --- /dev/null +++ b/src/test/resources/testfiles/data/dateOnlyWithTimeZoneMap.sdl @@ -0,0 +1,10 @@ +#Mapping values +#Tue Jun 20 18:59:17 PDT 2006 +ACCOUNTNUMBER__C=AccountNumber__c +NAME=Name +WEBSITE=Website +TYPE=Type +ANNUALREVENUE=AnnualRevenue +PHONE=Phone +ORACLE_ID__C=Oracle_Id__c +DATE_ONLY=CustomDateOnly__c diff --git a/src/test/resources/testfiles/data/dateUsingDefaultTimeZone.csv b/src/test/resources/testfiles/data/dateUsingDefaultTimeZone.csv index 7885eefb6..cd348b445 100644 --- a/src/test/resources/testfiles/data/dateUsingDefaultTimeZone.csv +++ b/src/test/resources/testfiles/data/dateUsingDefaultTimeZone.csv @@ -1,2 +1,2 @@ -NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE -account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T12:00:00.000 \ No newline at end of file +NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE,DATE_ONLY +account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T12:00:00.000,2010-10-14T12:00:00.000 diff --git a/src/test/resources/testfiles/data/dateUsingDefaultTimeZoneMap.sdl b/src/test/resources/testfiles/data/dateUsingDefaultTimeZoneMap.sdl index ace7819f5..1eae31d78 100644 --- a/src/test/resources/testfiles/data/dateUsingDefaultTimeZoneMap.sdl +++ b/src/test/resources/testfiles/data/dateUsingDefaultTimeZoneMap.sdl @@ -7,4 +7,5 @@ TYPE=Type ANNUALREVENUE=AnnualRevenue PHONE=Phone ORACLE_ID__C=Oracle_Id__c -DATE=CustomDateTime__c \ No newline at end of file +DATE=CustomDateTime__c +DATE_ONLY=CustomDateOnly__c diff --git a/src/test/resources/testfiles/data/dateWithTimeZone.csv b/src/test/resources/testfiles/data/dateWithTimeZone.csv index 94d222905..18382c248 100644 --- a/src/test/resources/testfiles/data/dateWithTimeZone.csv +++ b/src/test/resources/testfiles/data/dateWithTimeZone.csv @@ -1,2 +1,3 @@ -NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE -account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T12:00:00.000Z-0300 \ No newline at end of file +NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,DATE,DATE_ONLY +account insert #0,Account,415-555-0000,ACCT_0,http://www.accountInsert0.com,0,1-00000,2010-10-14T12:00:00.000Z-0300,2010-10-14T12:00:00.000Z +account insert #1,Account,415-555-0000,ACCT_1,http://www.accountInsert1.com,0,1-00001,2010-10-14T12:00:00.000Z-0300,2010-10-14T00:00:00.000 diff --git a/src/test/resources/testfiles/data/dateWithTimeZoneMap.sdl b/src/test/resources/testfiles/data/dateWithTimeZoneMap.sdl index ace7819f5..1eae31d78 100644 --- a/src/test/resources/testfiles/data/dateWithTimeZoneMap.sdl +++ b/src/test/resources/testfiles/data/dateWithTimeZoneMap.sdl @@ -7,4 +7,5 @@ TYPE=Type ANNUALREVENUE=AnnualRevenue PHONE=Phone ORACLE_ID__C=Oracle_Id__c -DATE=CustomDateTime__c \ No newline at end of file +DATE=CustomDateTime__c +DATE_ONLY=CustomDateOnly__c diff --git a/src/test/resources/testfiles/data/emptyFirstRowFieldValueInCsvMap.sdl b/src/test/resources/testfiles/data/emptyFirstRowFieldValueInCsvMap.sdl new file mode 100644 index 000000000..1520a1435 --- /dev/null +++ b/src/test/resources/testfiles/data/emptyFirstRowFieldValueInCsvMap.sdl @@ -0,0 +1,6 @@ +#Mapping values +#Mon May 16 17:24:40 PDT 2011 +Name=Name +Website=Website +ORACLE_ID__C=ORACLE_ID__C +ID=ID \ No newline at end of file diff --git a/src/test/resources/testfiles/data/emptyFirstRowFieldValueInCsvTemplate.csv b/src/test/resources/testfiles/data/emptyFirstRowFieldValueInCsvTemplate.csv new file mode 100644 index 000000000..c8936931b --- /dev/null +++ b/src/test/resources/testfiles/data/emptyFirstRowFieldValueInCsvTemplate.csv @@ -0,0 +1,3 @@ +ID,NAME,WEBSITE +001x00000035m0iAAA,account Update #0, +001x00000035m0jAAA,account Update #1,"updated" \ No newline at end of file diff --git a/src/test/resources/testfiles/data/extractAccountCsvMap.sdl b/src/test/resources/testfiles/data/extractAccountCsvMap.sdl index 56309aa33..085c71ff2 100644 --- a/src/test/resources/testfiles/data/extractAccountCsvMap.sdl +++ b/src/test/resources/testfiles/data/extractAccountCsvMap.sdl @@ -1,8 +1,8 @@ # extract account mapping values for query from salesforce (left) and update to csv file (right) # salesforceFieldName=csvFieldName -Id=account_id Oracle_Id__c=account_ext_id -Name=account_name Phone=account_phone AnnualRevenue=account_annual_revenue -AccountNumber__c=account_number \ No newline at end of file +AccountNumber__c=account_number +Name=account_name +Id=account_id \ No newline at end of file diff --git a/src/test/resources/testfiles/data/insertAccountWithMultipleBatchesCSV.csv b/src/test/resources/testfiles/data/insertAccountWithMultipleBatchesCSV.csv new file mode 100644 index 000000000..0e77813b5 --- /dev/null +++ b/src/test/resources/testfiles/data/insertAccountWithMultipleBatchesCSV.csv @@ -0,0 +1,4 @@ +oracle_id,name,CustomDateTime +o1,o1acct,"2010-10-14T12:00:00.000GMT" +o2,o2acct,wrongdate +o3,o3acct,"2010-10-14T12:00:00.000GMT" \ No newline at end of file diff --git a/src/test/resources/testfiles/data/insertAccountWithMultipleBatchesCSVMap.sdl b/src/test/resources/testfiles/data/insertAccountWithMultipleBatchesCSVMap.sdl new file mode 100644 index 000000000..772d61476 --- /dev/null +++ b/src/test/resources/testfiles/data/insertAccountWithMultipleBatchesCSVMap.sdl @@ -0,0 +1,5 @@ +#Mapping values +#Tue Jun 20 18:59:17 PDT 2006 +oracle_id=oracle_id__c +name=name +CustomDateTime=CustomDateTime__c \ No newline at end of file diff --git a/src/test/resources/testfiles/data/insertTaskWithContactAsWhoCsv.csv b/src/test/resources/testfiles/data/insertTaskWithContactAsWhoCsv.csv new file mode 100644 index 000000000..52a52f825 --- /dev/null +++ b/src/test/resources/testfiles/data/insertTaskWithContactAsWhoCsv.csv @@ -0,0 +1,2 @@ +Subject,Who:Contact-email +testPolymorphicLookup,contactFor@PolymorphicMappingOfTask.com \ No newline at end of file diff --git a/src/test/resources/testfiles/data/insertTaskWithContactAsWhoCsvMap.sdl b/src/test/resources/testfiles/data/insertTaskWithContactAsWhoCsvMap.sdl new file mode 100644 index 000000000..b5c4c95ad --- /dev/null +++ b/src/test/resources/testfiles/data/insertTaskWithContactAsWhoCsvMap.sdl @@ -0,0 +1,3 @@ +#Mapping values +Subject=Subject +Who\:Contact-Email=Who\:Contact-Email \ No newline at end of file diff --git a/src/test/resources/testfiles/data/nonMappedFieldsPermittedInDLTransactionMap.sdl b/src/test/resources/testfiles/data/nonMappedFieldsPermittedInDLTransactionMap.sdl index 46fd788cc..89a0f4158 100644 --- a/src/test/resources/testfiles/data/nonMappedFieldsPermittedInDLTransactionMap.sdl +++ b/src/test/resources/testfiles/data/nonMappedFieldsPermittedInDLTransactionMap.sdl @@ -4,3 +4,4 @@ #Industry = Industry Name=Name NumEmp=NumberOfEmployees +RichText=RichText__c \ No newline at end of file diff --git a/src/test/resources/testfiles/data/oneToManySforceFieldsMappingInCsv.csv b/src/test/resources/testfiles/data/oneToManySforceFieldsMappingInCsv.csv new file mode 100644 index 000000000..cc6a027ae --- /dev/null +++ b/src/test/resources/testfiles/data/oneToManySforceFieldsMappingInCsv.csv @@ -0,0 +1,3 @@ +"Name","NumEmployees","State" +"ABC Corp.",25000,"California" +"XYZ Corp.",62999,"New York" diff --git a/src/test/resources/testfiles/data/oneToManySforceFieldsMappingInCsvMap.sdl b/src/test/resources/testfiles/data/oneToManySforceFieldsMappingInCsvMap.sdl new file mode 100644 index 000000000..47c9dd4ad --- /dev/null +++ b/src/test/resources/testfiles/data/oneToManySforceFieldsMappingInCsvMap.sdl @@ -0,0 +1,6 @@ +#Mapping values +#Mon May 16 17:24:40 PDT 2011 +Name=Name,RICHTEXT__c +NumEmployees = NumberOfEmployees +"Aerospace"=Industry +State=BillingState, ShippingState, Description diff --git a/src/test/resources/testfiles/data/polymorphicRelationshipInAttachment.csv b/src/test/resources/testfiles/data/polymorphicRelationshipInAttachment.csv new file mode 100644 index 000000000..86cec899c --- /dev/null +++ b/src/test/resources/testfiles/data/polymorphicRelationshipInAttachment.csv @@ -0,0 +1,3 @@ +Name,Body,Parent:Account-Oracle_Id__c,Parent:Contact-Email +accountAttachment.txt,"target/test-classes/testfiles/data/attachment.txt","1-000000","" +contactAttachment.txt,"target/test-classes/testfiles/data/attachment.txt","","contact0@testcustomer.com" \ No newline at end of file diff --git a/src/test/resources/testfiles/data/polymorphicRelationshipInAttachmentMap.sdl b/src/test/resources/testfiles/data/polymorphicRelationshipInAttachmentMap.sdl new file mode 100644 index 000000000..45f053708 --- /dev/null +++ b/src/test/resources/testfiles/data/polymorphicRelationshipInAttachmentMap.sdl @@ -0,0 +1,6 @@ +# extract account mapping values for query from salesforce (left) and update to csv file (right) +# salesforceFieldName=csvFieldName +Name=Name +Body=Body +Parent\:Account-Oracle_Id__c=Parent\:Account-Oracle_Id__c +Parent\:Contact-Email=Parent\:Contact-Email \ No newline at end of file diff --git a/src/test/resources/testfiles/data/selectFieldsSoqlMap.sdl b/src/test/resources/testfiles/data/selectFieldsSoqlMap.sdl new file mode 100644 index 000000000..540fa8de7 --- /dev/null +++ b/src/test/resources/testfiles/data/selectFieldsSoqlMap.sdl @@ -0,0 +1,5 @@ +# extract account mapping values for query from salesforce (left) and update to csv file (right) +# salesforceFieldName=csvFieldName +Id=testfield_id +Name=name_to_test +testfield__c=customfield \ No newline at end of file diff --git a/src/test/resources/testfiles/data/updateAccountCsvTemplate.csv b/src/test/resources/testfiles/data/updateAccountCsvTemplate.csv index 1e487b1d8..1fb73f9ce 100644 --- a/src/test/resources/testfiles/data/updateAccountCsvTemplate.csv +++ b/src/test/resources/testfiles/data/updateAccountCsvTemplate.csv @@ -1,101 +1,101 @@ -ID,NAME,WEBSITE,ANNUALREVENUE -001x00000035m0iAAA,account Update #0,http://www.accountUpdate0.com,0 -001x00000035m0jAAA,account Update #1,http://www.accountUpdate1.com,1000 -001x00000035m0kAAA,account Update #2,http://www.accountUpdate2.com,2000 -001x00000035m0lAAA,account Update #3,http://www.accountUpdate3.com,3000 -001x00000035m0mAAA,account Update #4,http://www.accountUpdate4.com,4000 -001x00000035m0nAAA,account Update #5,http://www.accountUpdate5.com,5000 -001x00000035m0oAAA,account Update #6,http://www.accountUpdate6.com,6000 -001x00000035m0pAAA,account Update #7,http://www.accountUpdate7.com,7000 -001x00000035m0qAAA,account Update #8,http://www.accountUpdate8.com,8000 -001x00000035m0rAAA,account Update #9,http://www.accountUpdate9.com,9000 -001x00000035m0sAAA,account Update #10,http://www.accountUpdate10.com,10000 -001x00000035m0tAAA,account Update #11,http://www.accountUpdate11.com,11000 -001x00000035m0uAAA,account Update #12,http://www.accountUpdate12.com,12000 -001x00000035m0vAAA,account Update #13,http://www.accountUpdate13.com,13000 -001x00000035m0wAAA,account Update #14,http://www.accountUpdate14.com,14000 -001x00000035m0xAAA,account Update #15,http://www.accountUpdate15.com,15000 -001x00000035m0yAAA,account Update #16,http://www.accountUpdate16.com,16000 -001x00000035m0zAAA,account Update #17,http://www.accountUpdate17.com,17000 -001x00000035m10AAA,account Update #18,http://www.accountUpdate18.com,18000 -001x00000035m11AAA,account Update #19,http://www.accountUpdate19.com,19000 -001x00000035m12AAA,account Update #20,http://www.accountUpdate20.com,20000 -001x00000035m13AAA,account Update #21,http://www.accountUpdate21.com,21000 -001x00000035m14AAA,account Update #22,http://www.accountUpdate22.com,22000 -001x00000035m15AAA,account Update #23,http://www.accountUpdate23.com,23000 -001x00000035m16AAA,account Update #24,http://www.accountUpdate24.com,24000 -001x00000035m17AAA,account Update #25,http://www.accountUpdate25.com,25000 -001x00000035m18AAA,account Update #26,http://www.accountUpdate26.com,26000 -001x00000035m19AAA,account Update #27,http://www.accountUpdate27.com,27000 -001x00000035m1AAAQ,account Update #28,http://www.accountUpdate28.com,28000 -001x00000035m1BAAQ,account Update #29,http://www.accountUpdate29.com,29000 -001x00000035m1CAAQ,account Update #30,http://www.accountUpdate30.com,30000 -001x00000035m1DAAQ,account Update #31,http://www.accountUpdate31.com,31000 -001x00000035m1EAAQ,account Update #32,http://www.accountUpdate32.com,32000 -001x00000035m1FAAQ,account Update #33,http://www.accountUpdate33.com,33000 -001x00000035m1GAAQ,account Update #34,http://www.accountUpdate34.com,34000 -001x00000035m1HAAQ,account Update #35,http://www.accountUpdate35.com,35000 -001x00000035m1IAAQ,account Update #36,http://www.accountUpdate36.com,36000 -001x00000035m1JAAQ,account Update #37,http://www.accountUpdate37.com,37000 -001x00000035m1KAAQ,account Update #38,http://www.accountUpdate38.com,38000 -001x00000035m1LAAQ,account Update #39,http://www.accountUpdate39.com,39000 -001x00000035m1MAAQ,account Update #40,http://www.accountUpdate40.com,40000 -001x00000035m1NAAQ,account Update #41,http://www.accountUpdate41.com,41000 -001x00000035m1OAAQ,account Update #42,http://www.accountUpdate42.com,42000 -001x00000035m1PAAQ,account Update #43,http://www.accountUpdate43.com,43000 -001x00000035m1QAAQ,account Update #44,http://www.accountUpdate44.com,44000 -001x00000035m1RAAQ,account Update #45,http://www.accountUpdate45.com,45000 -001x00000035m1SAAQ,account Update #46,http://www.accountUpdate46.com,46000 -001x00000035m1TAAQ,account Update #47,http://www.accountUpdate47.com,47000 -001x00000035m1UAAQ,account Update #48,http://www.accountUpdate48.com,48000 -001x00000035m1VAAQ,account Update #49,http://www.accountUpdate49.com,49000 -001x00000035m1WAAQ,account Update #50,http://www.accountUpdate50.com,50000 -001x00000035m1XAAQ,account Update #51,http://www.accountUpdate51.com,51000 -001x00000035m1YAAQ,account Update #52,http://www.accountUpdate52.com,52000 -001x00000035m1ZAAQ,account Update #53,http://www.accountUpdate53.com,53000 -001x00000035m1aAAA,account Update #54,http://www.accountUpdate54.com,54000 -001x00000035m1bAAA,account Update #55,http://www.accountUpdate55.com,55000 -001x00000035m1cAAA,account Update #56,http://www.accountUpdate56.com,56000 -001x00000035m1dAAA,account Update #57,http://www.accountUpdate57.com,57000 -001x00000035m1eAAA,account Update #58,http://www.accountUpdate58.com,58000 -001x00000035m1fAAA,account Update #59,http://www.accountUpdate59.com,59000 -001x00000035m1gAAA,account Update #60,http://www.accountUpdate60.com,60000 -001x00000035m1hAAA,account Update #61,http://www.accountUpdate61.com,61000 -001x00000035m1iAAA,account Update #62,http://www.accountUpdate62.com,62000 -001x00000035m1jAAA,account Update #63,http://www.accountUpdate63.com,63000 -001x00000035m1kAAA,account Update #64,http://www.accountUpdate64.com,64000 -001x00000035m1lAAA,account Update #65,http://www.accountUpdate65.com,65000 -001x00000035m1mAAA,account Update #66,http://www.accountUpdate66.com,66000 -001x00000035m1nAAA,account Update #67,http://www.accountUpdate67.com,67000 -001x00000035m1oAAA,account Update #68,http://www.accountUpdate68.com,68000 -001x00000035m1pAAA,account Update #69,http://www.accountUpdate69.com,69000 -001x00000035m1qAAA,account Update #70,http://www.accountUpdate70.com,70000 -001x00000035m1rAAA,account Update #71,http://www.accountUpdate71.com,71000 -001x00000035m1sAAA,account Update #72,http://www.accountUpdate72.com,72000 -001x00000035m1tAAA,account Update #73,http://www.accountUpdate73.com,73000 -001x00000035m1uAAA,account Update #74,http://www.accountUpdate74.com,74000 -001x00000035m1vAAA,account Update #75,http://www.accountUpdate75.com,75000 -001x00000035m1wAAA,account Update #76,http://www.accountUpdate76.com,76000 -001x00000035m1xAAA,account Update #77,http://www.accountUpdate77.com,77000 -001x00000035m1yAAA,account Update #78,http://www.accountUpdate78.com,78000 -001x00000035m1zAAA,account Update #79,http://www.accountUpdate79.com,79000 -001x00000035m20AAA,account Update #80,http://www.accountUpdate80.com,80000 -001x00000035m21AAA,account Update #81,http://www.accountUpdate81.com,81000 -001x00000035m22AAA,account Update #82,http://www.accountUpdate82.com,82000 -001x00000035m23AAA,account Update #83,http://www.accountUpdate83.com,83000 -001x00000035m24AAA,account Update #84,http://www.accountUpdate84.com,84000 -001x00000035m25AAA,account Update #85,http://www.accountUpdate85.com,85000 -001x00000035m26AAA,account Update #86,http://www.accountUpdate86.com,86000 -001x00000035m27AAA,account Update #87,http://www.accountUpdate87.com,87000 -001x00000035m28AAA,account Update #88,http://www.accountUpdate88.com,88000 -001x00000035m29AAA,account Update #89,http://www.accountUpdate89.com,89000 -001x00000035m2AAAQ,account Update #90,http://www.accountUpdate90.com,90000 -001x00000035m2BAAQ,account Update #91,http://www.accountUpdate91.com,91000 -001x00000035m2CAAQ,account Update #92,http://www.accountUpdate92.com,92000 -001x00000035m2DAAQ,account Update #93,http://www.accountUpdate93.com,93000 -001x00000035m2EAAQ,account Update #94,http://www.accountUpdate94.com,94000 -001x00000035m2FAAQ,account Update #95,http://www.accountUpdate95.com,95000 -001x00000035m2GAAQ,account Update #96,http://www.accountUpdate96.com,96000 -001x00000035m2HAAQ,account Update #97,http://www.accountUpdate97.com,97000 -001x00000035m2IAAQ,account Update #98,http://www.accountUpdate98.com,98000 -001x00000035m2JAAQ,account Update #99,http://www.accountUpdate99.com,99000 +ID,NAME,WEBSITE,ANNUALREVENUE,PHONE +001x00000035m0iAAA,account Update #0,http://www.accountUpdate0.com,0,1234567890 +001x00000035m0jAAA,account Update #1,http://www.accountUpdate1.com,1000,1234567890 +001x00000035m0kAAA,account Update #2,http://www.accountUpdate2.com,2000,1234567890 +001x00000035m0lAAA,account Update #3,http://www.accountUpdate3.com,3000,1234567890 +001x00000035m0mAAA,account Update #4,http://www.accountUpdate4.com,4000,1234567890 +001x00000035m0nAAA,account Update #5,http://www.accountUpdate5.com,5000,1234567890 +001x00000035m0oAAA,account Update #6,http://www.accountUpdate6.com,6000,1234567890 +001x00000035m0pAAA,account Update #7,http://www.accountUpdate7.com,7000,1234567890 +001x00000035m0qAAA,account Update #8,http://www.accountUpdate8.com,8000,1234567890 +001x00000035m0rAAA,account Update #9,http://www.accountUpdate9.com,9000,1234567890 +001x00000035m0sAAA,account Update #10,http://www.accountUpdate10.com,10000,1234567890 +001x00000035m0tAAA,account Update #11,http://www.accountUpdate11.com,11000,1234567890 +001x00000035m0uAAA,account Update #12,http://www.accountUpdate12.com,12000,1234567890 +001x00000035m0vAAA,account Update #13,http://www.accountUpdate13.com,13000,1234567890 +001x00000035m0wAAA,account Update #14,http://www.accountUpdate14.com,14000,1234567890 +001x00000035m0xAAA,account Update #15,http://www.accountUpdate15.com,15000,1234567890 +001x00000035m0yAAA,account Update #16,http://www.accountUpdate16.com,16000,1234567890 +001x00000035m0zAAA,account Update #17,http://www.accountUpdate17.com,17000,1234567890 +001x00000035m10AAA,account Update #18,http://www.accountUpdate18.com,18000,1234567890 +001x00000035m11AAA,account Update #19,http://www.accountUpdate19.com,19000,1234567890 +001x00000035m12AAA,account Update #20,http://www.accountUpdate20.com,20000,1234567890 +001x00000035m13AAA,account Update #21,http://www.accountUpdate21.com,21000,1234567890 +001x00000035m14AAA,account Update #22,http://www.accountUpdate22.com,22000,1234567890 +001x00000035m15AAA,account Update #23,http://www.accountUpdate23.com,23000,1234567890 +001x00000035m16AAA,account Update #24,http://www.accountUpdate24.com,24000,1234567890 +001x00000035m17AAA,account Update #25,http://www.accountUpdate25.com,25000,1234567890 +001x00000035m18AAA,account Update #26,http://www.accountUpdate26.com,26000,1234567890 +001x00000035m19AAA,account Update #27,http://www.accountUpdate27.com,27000,1234567890 +001x00000035m1AAAQ,account Update #28,http://www.accountUpdate28.com,28000,1234567890 +001x00000035m1BAAQ,account Update #29,http://www.accountUpdate29.com,29000,1234567890 +001x00000035m1CAAQ,account Update #30,http://www.accountUpdate30.com,30000,1234567890 +001x00000035m1DAAQ,account Update #31,http://www.accountUpdate31.com,31000,1234567890 +001x00000035m1EAAQ,account Update #32,http://www.accountUpdate32.com,32000,1234567890 +001x00000035m1FAAQ,account Update #33,http://www.accountUpdate33.com,33000,1234567890 +001x00000035m1GAAQ,account Update #34,http://www.accountUpdate34.com,34000,1234567890 +001x00000035m1HAAQ,account Update #35,http://www.accountUpdate35.com,35000,1234567890 +001x00000035m1IAAQ,account Update #36,http://www.accountUpdate36.com,36000,1234567890 +001x00000035m1JAAQ,account Update #37,http://www.accountUpdate37.com,37000,1234567890 +001x00000035m1KAAQ,account Update #38,http://www.accountUpdate38.com,38000,1234567890 +001x00000035m1LAAQ,account Update #39,http://www.accountUpdate39.com,39000,1234567890 +001x00000035m1MAAQ,account Update #40,http://www.accountUpdate40.com,40000,1234567890 +001x00000035m1NAAQ,account Update #41,http://www.accountUpdate41.com,41000,1234567890 +001x00000035m1OAAQ,account Update #42,http://www.accountUpdate42.com,42000,1234567890 +001x00000035m1PAAQ,account Update #43,http://www.accountUpdate43.com,43000,1234567890 +001x00000035m1QAAQ,account Update #44,http://www.accountUpdate44.com,44000,1234567890 +001x00000035m1RAAQ,account Update #45,http://www.accountUpdate45.com,45000,1234567890 +001x00000035m1SAAQ,account Update #46,http://www.accountUpdate46.com,46000,1234567890 +001x00000035m1TAAQ,account Update #47,http://www.accountUpdate47.com,47000,1234567890 +001x00000035m1UAAQ,account Update #48,http://www.accountUpdate48.com,48000,1234567890 +001x00000035m1VAAQ,account Update #49,http://www.accountUpdate49.com,49000,1234567890 +001x00000035m1WAAQ,account Update #50,http://www.accountUpdate50.com,50000,1234567890 +001x00000035m1XAAQ,account Update #51,http://www.accountUpdate51.com,51000,1234567890 +001x00000035m1YAAQ,account Update #52,http://www.accountUpdate52.com,52000,1234567890 +001x00000035m1ZAAQ,account Update #53,http://www.accountUpdate53.com,53000,1234567890 +001x00000035m1aAAA,account Update #54,http://www.accountUpdate54.com,54000,1234567890 +001x00000035m1bAAA,account Update #55,http://www.accountUpdate55.com,55000,1234567890 +001x00000035m1cAAA,account Update #56,http://www.accountUpdate56.com,56000,1234567890 +001x00000035m1dAAA,account Update #57,http://www.accountUpdate57.com,57000,1234567890 +001x00000035m1eAAA,account Update #58,http://www.accountUpdate58.com,58000,1234567890 +001x00000035m1fAAA,account Update #59,http://www.accountUpdate59.com,59000,1234567890 +001x00000035m1gAAA,account Update #60,http://www.accountUpdate60.com,60000,1234567890 +001x00000035m1hAAA,account Update #61,http://www.accountUpdate61.com,61000,1234567890 +001x00000035m1iAAA,account Update #62,http://www.accountUpdate62.com,62000,1234567890 +001x00000035m1jAAA,account Update #63,http://www.accountUpdate63.com,63000,1234567890 +001x00000035m1kAAA,account Update #64,http://www.accountUpdate64.com,64000,1234567890 +001x00000035m1lAAA,account Update #65,http://www.accountUpdate65.com,65000,1234567890 +001x00000035m1mAAA,account Update #66,http://www.accountUpdate66.com,66000,1234567890 +001x00000035m1nAAA,account Update #67,http://www.accountUpdate67.com,67000,1234567890 +001x00000035m1oAAA,account Update #68,http://www.accountUpdate68.com,68000,1234567890 +001x00000035m1pAAA,account Update #69,http://www.accountUpdate69.com,69000,1234567890 +001x00000035m1qAAA,account Update #70,http://www.accountUpdate70.com,70000,1234567890 +001x00000035m1rAAA,account Update #71,http://www.accountUpdate71.com,71000,1234567890 +001x00000035m1sAAA,account Update #72,http://www.accountUpdate72.com,72000,1234567890 +001x00000035m1tAAA,account Update #73,http://www.accountUpdate73.com,73000,1234567890 +001x00000035m1uAAA,account Update #74,http://www.accountUpdate74.com,74000,1234567890 +001x00000035m1vAAA,account Update #75,http://www.accountUpdate75.com,75000,1234567890 +001x00000035m1wAAA,account Update #76,http://www.accountUpdate76.com,76000,1234567890 +001x00000035m1xAAA,account Update #77,http://www.accountUpdate77.com,77000,1234567890 +001x00000035m1yAAA,account Update #78,http://www.accountUpdate78.com,78000,1234567890 +001x00000035m1zAAA,account Update #79,http://www.accountUpdate79.com,79000,1234567890 +001x00000035m20AAA,account Update #80,http://www.accountUpdate80.com,80000,1234567890 +001x00000035m21AAA,account Update #81,http://www.accountUpdate81.com,81000,1234567890 +001x00000035m22AAA,account Update #82,http://www.accountUpdate82.com,82000,1234567890 +001x00000035m23AAA,account Update #83,http://www.accountUpdate83.com,83000,1234567890 +001x00000035m24AAA,account Update #84,http://www.accountUpdate84.com,84000,1234567890 +001x00000035m25AAA,account Update #85,http://www.accountUpdate85.com,85000,1234567890 +001x00000035m26AAA,account Update #86,http://www.accountUpdate86.com,86000,1234567890 +001x00000035m27AAA,account Update #87,http://www.accountUpdate87.com,87000,1234567890 +001x00000035m28AAA,account Update #88,http://www.accountUpdate88.com,88000,1234567890 +001x00000035m29AAA,account Update #89,http://www.accountUpdate89.com,89000,1234567890 +001x00000035m2AAAQ,account Update #90,http://www.accountUpdate90.com,90000,1234567890 +001x00000035m2BAAQ,account Update #91,http://www.accountUpdate91.com,91000,1234567890 +001x00000035m2CAAQ,account Update #92,http://www.accountUpdate92.com,92000,1234567890 +001x00000035m2DAAQ,account Update #93,http://www.accountUpdate93.com,93000,1234567890 +001x00000035m2EAAQ,account Update #94,http://www.accountUpdate94.com,94000,1234567890 +001x00000035m2FAAQ,account Update #95,http://www.accountUpdate95.com,95000,1234567890 +001x00000035m2GAAQ,account Update #96,http://www.accountUpdate96.com,96000,1234567890 +001x00000035m2HAAQ,account Update #97,http://www.accountUpdate97.com,97000,1234567890 +001x00000035m2IAAQ,account Update #98,http://www.accountUpdate98.com,98000,1234567890 +001x00000035m2JAAQ,account Update #99,http://www.accountUpdate99.com,99000,1234567890 diff --git a/src/test/resources/testfiles/data/updateAccountWithExternalIdCsvMap.sdl b/src/test/resources/testfiles/data/updateAccountWithExternalIdCsvMap.sdl new file mode 100644 index 000000000..e032655a8 --- /dev/null +++ b/src/test/resources/testfiles/data/updateAccountWithExternalIdCsvMap.sdl @@ -0,0 +1,10 @@ +#Mapping values +#Tue Jun 20 18:59:17 PDT 2006 +ACCOUNTNUMBER__C=AccountNumber__c +NAME=Name +WEBSITE=Website +TYPE=Type +ANNUALREVENUE=AnnualRevenue +PHONE=Phone +ORACLE_ID__C=Oracle_Id__c +OWNER\:User-username=owner\:user-username \ No newline at end of file diff --git a/src/test/resources/testfiles/data/updateAccountWithExternalIdCsvTemplate.csv b/src/test/resources/testfiles/data/updateAccountWithExternalIdCsvTemplate.csv new file mode 100644 index 000000000..e260813d7 --- /dev/null +++ b/src/test/resources/testfiles/data/updateAccountWithExternalIdCsvTemplate.csv @@ -0,0 +1,6 @@ +NAME,WEBSITE,ANNUALREVENUE,PHONE,ORACLE_ID__C,OWNER:User-username +account Update #0,http://www.accountUpdate0.com,0,1234567890,1-000000,standard@org.com +account Update #1,http://www.accountUpdate1.com,1000,1234567890,1-000001,standard@org.com +account Update #2,http://www.accountUpdate2.com,2000,1234567890,1-000002,standard@org.com +account Update #3,http://www.accountUpdate3.com,3000,1234567890,1-000003,standard@org.com +account Update #4,http://www.accountUpdate4.com,4000,1234567890,1-000004,standard@org.com \ No newline at end of file diff --git a/src/test/resources/testfiles/data/upsertFkAccountCsvMap.sdl b/src/test/resources/testfiles/data/upsertFkAccountCsvMap.sdl deleted file mode 100644 index cd1b3bae5..000000000 --- a/src/test/resources/testfiles/data/upsertFkAccountCsvMap.sdl +++ /dev/null @@ -1,10 +0,0 @@ -#Mapping values -#Tue Jun 20 18:59:17 PDT 2006 -ACCOUNTNUMBER__C=AccountNumber__c -NAME=Name -WEBSITE=Website -TYPE=Type -ANNUALREVENUE=AnnualRevenue -PHONE=Phone -ORACLE_ID__C=Oracle_Id__c -PARENT=Parent:Oracle_Id__c diff --git a/src/test/resources/testfiles/data/upsertFkAccountNewFormatCsvMap.sdl b/src/test/resources/testfiles/data/upsertFkAccountNewFormatCsvMap.sdl new file mode 100644 index 000000000..87ff687ee --- /dev/null +++ b/src/test/resources/testfiles/data/upsertFkAccountNewFormatCsvMap.sdl @@ -0,0 +1,10 @@ +#Mapping values +#Tue Jun 20 18:59:17 PDT 2006 +ACCOUNTNUMBER__C=AccountNumber__c +NAME=Name +WEBSITE=Website +TYPE=Type +ANNUALREVENUE=AnnualRevenue +PHONE=Phone +ORACLE_ID__C=Oracle_Id__c +PARENT=Parent\:Account-Oracle_Id__c diff --git a/src/test/resources/testfiles/data/upsertFkAccountNewFormatCsvTemplate.csv b/src/test/resources/testfiles/data/upsertFkAccountNewFormatCsvTemplate.csv new file mode 100644 index 000000000..59c88186d --- /dev/null +++ b/src/test/resources/testfiles/data/upsertFkAccountNewFormatCsvTemplate.csv @@ -0,0 +1,6 @@ +NAME,TYPE,PHONE,ACCOUNTNUMBER__C,WEBSITE,ANNUALREVENUE,ORACLE_ID__C,PARENT +account UpsertFk #05,Account,415-555-0005,ACCT_05,http://www.accountUpsertFk05.com,05000,1-000005,1-000000 +account UpsertFk #06,Account,415-555-0006,ACCT_06,http://www.accountUpsertFk06.com,06000,1-000006,1-000001 +account UpsertFk #07,Account,415-555-0007,ACCT_07,http://www.accountUpsertFk07.com,07000,1-000007,1-000002 +account UpsertFk #08,Account,415-555-0008,ACCT_08,http://www.accountUpsertFk08.com,08000,1-000008,1-000003 +account UpsertFk #09,Account,415-555-0009,ACCT_09,http://www.accountUpsertFk09.com,09000,1-000009,1-000004 \ No newline at end of file diff --git a/src/test/resources/testfiles/data/upsertFkAccountOldFormatCsvMap.sdl b/src/test/resources/testfiles/data/upsertFkAccountOldFormatCsvMap.sdl new file mode 100644 index 000000000..b47c9b010 --- /dev/null +++ b/src/test/resources/testfiles/data/upsertFkAccountOldFormatCsvMap.sdl @@ -0,0 +1,10 @@ +#Mapping values +#Tue Jun 20 18:59:17 PDT 2006 +ACCOUNTNUMBER__C=AccountNumber__c +NAME=Name +WEBSITE=Website +TYPE=Type +ANNUALREVENUE=AnnualRevenue +PHONE=Phone +ORACLE_ID__C=Oracle_Id__c +PARENT=pArent:oRaClE_Id__c diff --git a/src/test/resources/testfiles/data/upsertFkAccountCsvTemplate.csv b/src/test/resources/testfiles/data/upsertFkAccountOldFormatCsvTemplate.csv similarity index 100% rename from src/test/resources/testfiles/data/upsertFkAccountCsvTemplate.csv rename to src/test/resources/testfiles/data/upsertFkAccountOldFormatCsvTemplate.csv diff --git a/src/test/resources/testfilter.properties b/src/test/resources/testfilter.properties deleted file mode 100644 index 5ed4fecb2..000000000 --- a/src/test/resources/testfilter.properties +++ /dev/null @@ -1,15 +0,0 @@ -## DO NOT CHECK IN USERNAMES, PASSWORDS OR INTERNAL ENDPOINTS ON THIS FILE -## -## For CI, these properties are configured externally in a Jenkins configuration - -## endpoint MUST start with https -test.endpoint= - -## user/org/passwd -test.org= -test.user.default= -test.user.restricted= -# this should be the encrypted password, not plain text -test.password= - -testfiles.dir=${project.build.testOutputDirectory}/testfiles diff --git a/updateSWT.py b/updateSWT.py new file mode 100755 index 000000000..87d7e30c0 --- /dev/null +++ b/updateSWT.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python3 + +import requests, zipfile, io, shutil, os, sys +import subprocess +from bs4 import BeautifulSoup +from os.path import expanduser +import urllib.request +import re +import argparse +import atexit + +#################################################################### +# Prerequisites: +# - Python 3.9 or higher +# - Directory containing mvn command in PATH environment variable. +# - Python BeautifulSoup installed locally. Run 'pip3 install beautifulsoup4' +# - Python requests installed locally. Run 'pip3 install requests' +# +# Side-effects: +# - Zip content extracted in ~/Downloads directory +# - SWT jar files installed in /local-proj-repo subdirectories +# +# Follow-on manual steps: +# - Update version value for SWT dependencies in pom.xml with the downloaded SWT version. +# +# Outline of the steps taken: +# - Start at https://download.eclipse.org/eclipse/downloads/ +# - Go to "Latest Release" section +# - Click on the first link in the "Build Name" column +# - Go to "SWT Binary and Source" section +# - Click on the links next to "Windows (64 bit version)", "Mac OSX (64 bit version)", and "Mac OSX (64 bit version for Arm64/AArch64)" +# - Extract the contents of the zip file +# - Go to the extraction folder and run mvn install:install-file command +# +#################################################################### + +LOCAL_REPO_DIR = "./local-proj-repo" +LOCAL_REPO_SAVE_DIR = "../local-proj-repo-save" + +def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + +def cleanupBeforeExit(): + if os.path.isdir(LOCAL_REPO_SAVE_DIR): + # restore local SWT repo before exiting + shutil.move(LOCAL_REPO_SAVE_DIR + "/", LOCAL_REPO_DIR) + shutil.rmtree(LOCAL_REPO_SAVE_DIR) + +def exitWithError(errorStr): + print(errorStr) + sys.exit(-1) + +########################################################### + +def getSWTDownloadLinkForPlatform(soup, platformString): + results = soup.find(id="SWT").find_next("td").string + while results != None and results != platformString : + results = results.find_next("td").string + + if results == platformString : + results = results.find_next("a")['href'] + + return results + +######## end of getSWTDownloadLinkForPlatform ########## + +def downloadAndExtractZip(url): + zipfileName = url.split('=',1)[1] + + home = expanduser("~") + unzippedDirName = home + "/Downloads/" + zipfileName.removesuffix('.zip') + "/" +# print(unzippedDirName) + + page = requests.get(url) + soup = BeautifulSoup(page.content, "html.parser") + zipURL = soup.find("meta").find_next("a")['href'] +# print(zipURL) + + page = requests.get(zipURL) + soup = BeautifulSoup(page.content, "html.parser") + divWithZip = soup.find("div", {"class":"mirror-well"}) + zipURL = divWithZip.find_next("a")['href'] + zipURL = "https://www.eclipse.org/downloads/" + zipURL +# print(zipURL) + + # navigate the redirect to the actual mirror + page = requests.get(zipURL) + soup = BeautifulSoup(page.content, "html.parser") + zipURL = soup.find('meta', attrs={'http-equiv': 'Refresh'})['content'].split(';')[1].split('=')[1] +# print(zipURL) + + # delete existing content + if os.path.exists(unzippedDirName) and os.path.isdir(unzippedDirName): + shutil.rmtree(unzippedDirName) + response = requests.get(zipURL, stream=True) + z = zipfile.ZipFile(io.BytesIO(response.content)) + z.extractall(unzippedDirName) + subprocess.run(["zip", + "-d", + unzippedDirName + "swt.jar", + "META-INF/ECLIPSE_.SF", + "META-INF/ECLIPSE_.DSA", + "META-INF/ECLIPSE_.RSA"]) + + return unzippedDirName + +######## end of downloadAndExtractZip ########## + +def installInLocalMavenRepo(unzippedSWTDir, mvnArtifactId, gitCloneRootDir): +# command to execute + swtVersion = unzippedSWTDir.split('-')[1] +# print(swtVersion) + + if shutil.which("mvn") == None : + exitWithError("did not find mvn command in the execute path") + + + mavenCommand = "mvn install:install-file " \ + + "-Dfile=" + unzippedSWTDir + "swt.jar " \ + + "-DgroupId=local.swt " \ + + "-DartifactId=" + mvnArtifactId + " " \ + + "-Dversion=" + swtVersion + " " \ + + "-Dpackaging=jar " \ + + "-Dmaven.repo.local=" + gitCloneRootDir + "/local-proj-repo" +# print(mavenCommand) + subprocess.run(mavenCommand, shell=True) + +######## end of installInLocalMavenRepo ########## + +def updateSWT(mvnArtifactId, downloadPageLabel, gitCloneRootDir, version, forceUpdate): + URL = "https://download.eclipse.org/eclipse/downloads/" + page = requests.get(URL) + + soup = BeautifulSoup(page.content, "html.parser") + linkToVersionDownload = "" + localSWTVersion = "" + + if os.path.isdir(LOCAL_REPO_SAVE_DIR + "/local/swt/" + mvnArtifactId): + subdirs = os.listdir(LOCAL_REPO_SAVE_DIR + "/local/swt/" + mvnArtifactId + "/") + for dir in subdirs : + localSWTVersion = dir + + if version == "" : + anchorElement = soup.find(id="Latest_Release").find_next("a") + linkToVersionDownload = anchorElement['href'] + version = anchorElement.text + + else: + for link in soup.findAll('a', href=True): + if version in link.text : + linkToVersionDownload = link['href'] + break + + if forceUpdate == False \ + and version.strip() == localSWTVersion.strip() \ + and os.path.isdir(LOCAL_REPO_SAVE_DIR + "/local/swt/" + mvnArtifactId) : + shutil.move(LOCAL_REPO_SAVE_DIR + "/local/swt/" + mvnArtifactId + "/", LOCAL_REPO_DIR+ "/local/swt/" + mvnArtifactId) + return + + if linkToVersionDownload == "" : + exitWithError("version " + version + " not found for download") + + + downloadsPage = URL + linkToVersionDownload + page = requests.get(downloadsPage) + soup = BeautifulSoup(page.content, "html.parser") + + results = getSWTDownloadLinkForPlatform(soup, downloadPageLabel) + unzippedDir = downloadAndExtractZip(downloadsPage + results) + installInLocalMavenRepo(unzippedDir, mvnArtifactId, gitCloneRootDir) + +######## end of updateSWTAndPOM ######################### + +atexit.register(cleanupBeforeExit) + +parser = argparse.ArgumentParser(description = "my parser") +parser.add_argument("-v", "--version", required = False, default = "") +parser.add_argument("-f", "--force", required = False, default = False, nargs='?', const=True) +parser.add_argument("-c", "--cloneroot", required = False, default = os.getcwd()) + +arguments = parser.parse_args() + +# initialize variables from arguments +version = arguments.version +rootdir = arguments.cloneroot +forceUpdate = arguments.force +localSWTVersion = "" + +# Save the local SWT repo before proceeding to update +if os.path.isdir(LOCAL_REPO_DIR): + shutil.move(LOCAL_REPO_DIR + "/", LOCAL_REPO_SAVE_DIR) + +# Windows +updateSWT("swtwin32_x86_64", "Windows (64 bit version)", rootdir, version, forceUpdate) + +# Mac x86 +updateSWT("swtmac_x86_64", "Mac OSX (64 bit version)", rootdir, version, forceUpdate) + +# Mac ARM +updateSWT("swtmac_aarch64", "Mac OSX (64 bit version for Arm64/AArch64)", rootdir, version, forceUpdate) + +# Linux x86 +updateSWT("swtlinux_x86_64", "Linux (64 bit version)", rootdir, version, forceUpdate) + +# Linux ARM +updateSWT("swtlinux_aarch64", "Linux (64 bit version for AArch64)", rootdir, version, forceUpdate) + +if os.path.isdir(LOCAL_REPO_SAVE_DIR): + shutil.rmtree(LOCAL_REPO_SAVE_DIR) + +for subdir in os.listdir(LOCAL_REPO_DIR): + if subdir != "local" : + shutil.rmtree(LOCAL_REPO_DIR + "/" + subdir) diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.cpp b/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.cpp deleted file mode 100644 index 53113c47e..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.cpp +++ /dev/null @@ -1,1444 +0,0 @@ - -/* AccessControl plugin for NSIS - * Copyright (C) 2003 Mathias Hasselmann - * - * This software is provided 'as-is', without any express or implied - * warranty. In no event will the authors be held liable for any damages - * arising from the use of this software. - * - * Permission is granted to anyone to use this software for any purpose, - * including commercial applications, and to alter it and redistribute it - * freely, subject to the following restrictions: - * - * 1. The origin of this software must not be misrepresented; you must not - * claim that you wrote the original software. If you use this software - * in a product, an acknowledgment in the product documentation would be - * appreciated but is not required. - * - * 2. Altered source versions must be plainly marked as such, and must not - * be misrepresented as being the original software. - * - * 3. This notice may not be removed or altered from any source - * distribution. - */ - -/* Modifications by Afrow UK, kichik and AndersK. - * See Docs\AccessControl\Readme.txt for full modifications list. - */ - -#define WIN32_LEAN_AND_MEAN -#ifdef _WIN64 -#define WINVER 0x502 -#else -#define WINVER 0x400 -#endif - -#include -#ifdef UNICODE -#include "nsis_unicode/pluginapi.h" -#else -#include "nsis_ansi/pluginapi.h" -#endif -#include -#include - -/***************************************************************************** - GLOBAL VARIABLES - *****************************************************************************/ - -HINSTANCE g_hInstance = NULL; -int g_string_size = 1024; -extra_parameters* g_extra = NULL; - -/***************************************************************************** - TYPE DEFINITIONS - *****************************************************************************/ - -typedef struct -{ - const TCHAR* name; - SE_OBJECT_TYPE type; - DWORD defaultInheritance; - const TCHAR** const permissionNames; - const DWORD* const permissionFlags; - const int permissionCount; -} -SchemeType; - -typedef struct -{ - BOOL noInherit; - HKEY hRootKey; -} -Options; - -typedef enum -{ - ChangeMode_Owner, - ChangeMode_Group -} -ChangeMode; - -/***************************************************************************** - UTILITIES - *****************************************************************************/ - -#define SIZE_OF_ARRAY(Array) (sizeof((Array)) / sizeof(*(Array))) -#define ARRAY_CONTAINS(Array, Index) (Index >= 0 && Index < SIZE_OF_ARRAY(Array)) - -void* LocalAllocZero(size_t cb) { return LocalAlloc(LPTR, cb); } -inline void* LocalAlloc(size_t cb) { return LocalAllocZero(cb); } - -/***************************************************************************** - PLUG-IN HANDLING - *****************************************************************************/ - -#define PUBLIC_FUNCTION(Name) \ -extern "C" void __declspec(dllexport) __cdecl Name(HWND hWndParent, int string_size, TCHAR* variables, stack_t** stacktop, extra_parameters* extra) \ -{ \ - EXDLL_INIT(); \ - g_string_size = string_size; \ - g_extra = extra; - -#define PUBLIC_FUNCTION_END \ -} - -void showerror_s(const TCHAR* fmt, TCHAR* arg) -{ - TCHAR* msg = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - if (msg) - { - wsprintf(msg, fmt, arg); - pushstring(msg); - LocalFree(msg); - } -} - -void showerror_d(const TCHAR* fmt, DWORD arg) -{ - TCHAR* msg = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - if (msg) - { - wsprintf(msg, fmt, arg); - pushstring(msg); - LocalFree(msg); - } -} - -#define ERRSTR_OOM TEXT("OutOfMemory") - -#define ABORT_s(x, y) \ - { showerror_s(TEXT(x), y); goto cleanup; } -#define ABORT_d(x, y) \ - { showerror_d(TEXT(x), y); goto cleanup; } -#define ABORT(x) \ - { pushstring(TEXT(x)); goto cleanup; } - -/***************************************************************************** - COMPATIBILITY UTILITIES - *****************************************************************************/ - -#define BuildExplicitAccessWithNameA BuildExplicitAccessWithNameT -#define BuildExplicitAccessWithNameW BuildExplicitAccessWithNameT -#if WINVER < 0x500 -#define ConvertStringSidToSidA Compat_ConvertStringSidToSidT -#define ConvertStringSidToSidW Compat_ConvertStringSidToSidT - -static FARPROC AdvApi32_GetProcAddress(LPCSTR Name) -{ - return GetProcAddress(LoadLibraryA("ADVAPI32"), Name); -} - -TCHAR* CharPos(TCHAR* szStr, int cchStrLen, TCHAR chFind) -{ - for (int i = 0; i < cchStrLen && NULL != *(szStr + i); i++) - if (*(szStr + i) == chFind) - return szStr + i; - return NULL; -} - -BYTE FromHex(TCHAR* szHex) -{ - int a = 0; - int b = 0; - - if (szHex[0] >= TEXT('0') && szHex[0] <= TEXT('9')) - a = szHex[0] - TEXT('0'); - else if (szHex[0] >= TEXT('A') && szHex[0] <= TEXT('F')) - a = szHex[0] - TEXT('A') + 10; - else if (szHex[0] >= TEXT('a') && szHex[0] <= TEXT('f')) - a = szHex[0] - TEXT('a') + 10; - - if (szHex[1] >= TEXT('0') && szHex[1] <= TEXT('9')) - b = szHex[1] - TEXT('0'); - else if (szHex[1] >= TEXT('A') && szHex[1] <= TEXT('F')) - b = szHex[1] - TEXT('A') + 10; - else if (szHex[1] >= TEXT('a') && szHex[1] <= TEXT('f')) - b = szHex[1] - TEXT('a') + 10; - - return a * 16 + b; -} - -static void SetIdentifierAuthority(SID_IDENTIFIER_AUTHORITY&sia, DWORD val32) -{ - sia.Value[5] = (BYTE)((val32 & 0x000000FF) >> 0); - sia.Value[4] = (BYTE)((val32 & 0x0000FF00) >> 8); - sia.Value[3] = (BYTE)((val32 & 0x00FF0000) >> 16); - sia.Value[2] = (BYTE)((val32 & 0xFF000000) >> 24); - sia.Value[1] = sia.Value[0] = 0; -} - -// Based on GetBinarySid function from http://www.codeguru.com/cpp/w-p/system/security/article.php/c5659. -BOOL Compat_ConvertStringSidToSidT(LPTSTR szSid, PSID* ppSid) -{ - // Try to call the real function on 2000+, - // this will enable support for more SID Strings (Those will not work on NT4 so script carefully) - FARPROC fp = AdvApi32_GetProcAddress(sizeof(TCHAR) > 1 ? "ConvertStringSidToSidW" : "ConvertStringSidToSidA"); - bool ret = fp && ((BOOL(WINAPI*)(LPCTSTR,PSID*))fp)(szSid,ppSid); - if (ret) return ret; - - *ppSid = NULL; - BYTE nByteAuthorityCount = 0; - PSID pSid = LocalAllocZero(8 + (sizeof(DWORD) * SID_MAX_SUB_AUTHORITIES)); - if (!pSid) return false; - - // Building a revision 1 SID in memory, the rest of the code assumes that pSid is zero filled! - ((char*)pSid)[0] = 1; - SID_IDENTIFIER_AUTHORITY &sidIA = *(SID_IDENTIFIER_AUTHORITY*) ((char*)pSid + 2); - DWORD *pSidSA = (DWORD*) ((char*)pSid + 8); - - static const struct { char id0, id1; BYTE ia, sa0, sa1; } sidmap[] = { - {'A'|32,'N'|32, (5), (7) , 0}, // NT AUTHORITY\ANONYMOUS LOGON - {'A'|32,'U'|32, (5), (11), 0}, // NT AUTHORITY\Authenticated Users - {'B'|32,'A'|32, (5), (32), (544)-500}, // BUILTIN\Administrators - {'B'|32,'U'|32, (5), (32), (545)-500}, // BUILTIN\Users - {'I'|32,'U'|32, (5), (4) , 0}, // NT AUTHORITY\INTERACTIVE - {'S'|32,'Y'|32, (5), (18), 0}, // NT AUTHORITY\SYSTEM - {'W'|32,'D'|32, (1), (0) , 0}, // Everyone - }; - // Try to lookup a SID string - for (int i = 0; i < SIZE_OF_ARRAY(sidmap); ++i) - { - if ((szSid[0]|32) != sidmap[i].id0 || (szSid[1]|32) != sidmap[i].id1 || szSid[2]) continue; - SetIdentifierAuthority(sidIA, sidmap[i].ia); - pSidSA[nByteAuthorityCount++] = sidmap[i].sa0; - if (sidmap[i].sa1) pSidSA[nByteAuthorityCount++] = (DWORD)sidmap[i].sa1 + 500; - goto done; - } - - // S-SID_REVISION- + identifierauthority- + subauthorities + NULL - - // Skip S - PTSTR ptr; - if (!(ptr = CharPos(szSid, lstrlen(szSid), TEXT('-')))) - return FALSE; - ptr++; - - // Skip SID_REVISION - if (!(ptr = CharPos(ptr, lstrlen(ptr), TEXT('-')))) - return FALSE; - ptr++; - - // Skip identifierauthority - PTSTR ptr1; - if (!(ptr1 = CharPos(ptr, lstrlen(ptr), TEXT('-')))) - return FALSE; - *ptr1 = 0; - - if ((*ptr == TEXT('0')) && (*(ptr+1) == TEXT('x'))) - { - sidIA.Value[0] = FromHex(ptr); - sidIA.Value[1] = FromHex(ptr + 2); - sidIA.Value[2] = FromHex(ptr + 4); - sidIA.Value[3] = FromHex(ptr + 8); - sidIA.Value[4] = FromHex(ptr + 10); - sidIA.Value[5] = FromHex(ptr + 12); - } - else - { - SetIdentifierAuthority(sidIA, myatou(ptr)); - } - - // Skip - - *ptr1 = TEXT('-'), ptr = ptr1, ptr1++; - - for (int i = 0; i < 8; i++) - { - // Get subauthority count. - if (!(ptr = CharPos(ptr, lstrlen(ptr), TEXT('-')))) break; - *ptr = 0, ptr++, nByteAuthorityCount++; - } - - for (int i = 0; i < nByteAuthorityCount; i++) - { - // Get subauthority. - pSidSA[i] = myatou(ptr1); - ptr1 += lstrlen(ptr1) + 1; - } - -done: - if (nByteAuthorityCount) - { - *ppSid = pSid, ((char*)pSid)[1] = nByteAuthorityCount; - return TRUE; - } - LocalFree(pSid); - return FALSE; -} - -// Based on GetTextualSid function from http://www.codeguru.com/cpp/w-p/system/security/article.php/c5659. -BOOL ConvertSidToStringSidNoAlloc(const PSID pSid, LPTSTR pszSid) -{ - // Validate the binary SID - if(!IsValidSid(pSid)) return FALSE; - - // Get the identifier authority value from the SID - PSID_IDENTIFIER_AUTHORITY psia = GetSidIdentifierAuthority(pSid); - - // Get the number of subauthorities in the SID. - DWORD dwSubAuthorities = *GetSidSubAuthorityCount(pSid); - - // Compute the buffer length - // S-SID_REVISION- + IdentifierAuthority- + subauthorities- + NULL - DWORD dwSidSize = (15 + 12 + (12 * dwSubAuthorities) + 1) * sizeof(TCHAR); - - // Add 'S' prefix and revision number to the string - dwSidSize = wsprintf(pszSid, TEXT("S-%lu-"), SID_REVISION); - - // Add SID identifier authority to the string. - if((psia->Value[0] != 0) || (psia->Value[1] != 0)) - { - dwSidSize += wsprintf(pszSid + lstrlen(pszSid), - TEXT("0x%02hx%02hx%02hx%02hx%02hx%02hx"), - (USHORT)psia->Value[0], - (USHORT)psia->Value[1], - (USHORT)psia->Value[2], - (USHORT)psia->Value[3], - (USHORT)psia->Value[4], - (USHORT)psia->Value[5]); - } - else - { - dwSidSize += wsprintf(pszSid + lstrlen(pszSid), - TEXT("%lu"), - (ULONG)(psia->Value[5]) + - (ULONG)(psia->Value[4] << 8) + - (ULONG)(psia->Value[3] << 16) + - (ULONG)(psia->Value[2] << 24)); - } - - // Add SID subauthorities to the string - for(DWORD dwCounter = 0; dwCounter < dwSubAuthorities; dwCounter++) - { - dwSidSize += wsprintf(pszSid + dwSidSize, TEXT("-%lu"), - *GetSidSubAuthority(pSid, dwCounter)); - } - - return TRUE; -} - -#ifndef UNICODE -#define GetNamedSecurityInfoA Delayload_GetNamedSecurityInfoA -#define SetEntriesInAclA Delayload_SetEntriesInAclA -#define SetNamedSecurityInfoA Delayload_SetNamedSecurityInfoA - -DWORD Delayload_GetNamedSecurityInfoA(LPSTR ON, SE_OBJECT_TYPE OT, SECURITY_INFORMATION SI, PSID*ppSO, PSID*ppSG, PACL*ppD, PACL*ppS, PSECURITY_DESCRIPTOR*ppSD) -{ - FARPROC fp = AdvApi32_GetProcAddress("GetNamedSecurityInfoA"); - if (!fp) return ERROR_NOT_SUPPORTED; - return ((DWORD(WINAPI*)(LPSTR,SE_OBJECT_TYPE,SECURITY_INFORMATION,PSID*,PSID*,PACL*,PACL*,PSECURITY_DESCRIPTOR*))fp)(ON,OT,SI,ppSO,ppSG,ppD,ppS,ppSD); -} - -DWORD Delayload_SetEntriesInAclA(ULONG c, PEXPLICIT_ACCESS_A pEA, PACL pOA, PACL*ppNA) -{ - FARPROC fp = AdvApi32_GetProcAddress("SetEntriesInAclA"); - if (!fp) return ERROR_NOT_SUPPORTED; - return ((DWORD(WINAPI*)(ULONG,PEXPLICIT_ACCESS_A,PACL,PACL*))fp)(c,pEA,pOA,ppNA); -} - -DWORD Delayload_SetNamedSecurityInfoA(LPSTR ON, SE_OBJECT_TYPE OT, SECURITY_INFORMATION SI, PSID pSO, PSID pSG, PACL pD, PACL pS) -{ - FARPROC fp = AdvApi32_GetProcAddress("SetNamedSecurityInfoA"); - if (!fp) return ERROR_NOT_SUPPORTED; - return ((DWORD(WINAPI*)(LPSTR,SE_OBJECT_TYPE,SECURITY_INFORMATION,PSID,PSID,PACL,PACL))fp)(ON,OT,SI,pSO,pSG,pD,pS); -} -#endif // ~!UNICODE -#else -BOOL ConvertSidToStringSidNoAlloc(const PSID pSid, LPTSTR pszSid) -{ - LPTSTR tmp; - if (!ConvertSidToStringSid(pSid, &tmp)) return false; - lstrcpy(pszSid, tmp), LocalFree(tmp); - return true; -} -#endif // ~WINVER < 0x500 - -static VOID BuildExplicitAccessWithNameT(PEXPLICIT_ACCESS pEA, LPTSTR TN, DWORD AP, ACCESS_MODE AM, DWORD Inheritance) -{ - TRUSTEE &t = pEA->Trustee; - t.pMultipleTrustee = NULL, t.MultipleTrusteeOperation = NO_MULTIPLE_TRUSTEE; - t.TrusteeForm = TRUSTEE_IS_NAME, t.TrusteeType = TRUSTEE_IS_UNKNOWN; - t.ptstrName = TN, pEA->grfInheritance = Inheritance; - pEA->grfAccessPermissions = AP, pEA->grfAccessMode = AM; -} - -/***************************************************************************** - STRING PARSERS - *****************************************************************************/ - -/** Converts a string into an enumeration index. If the enumeration - ** contains the string the index of the string within the enumeration - ** is return. On error you'll receive -1. - **/ -static int ParseEnum(const TCHAR* keywords[], const TCHAR* str) -{ - const TCHAR** key; - - for(key = keywords; *key; ++key) - if (!lstrcmpi(str, *key)) return (int)(key - keywords); - - return -1; -} - -/** Parses a trustee string. If enclosed in brackets the string contains - ** a string SID. Otherwise it's assumed that the string contains a - ** trustee name. - **/ -static TCHAR* ParseTrustee(TCHAR* trustee, DWORD* trusteeForm) -{ - TCHAR* strend = trustee + lstrlen(trustee) - 1; - - if (TEXT('(') == *trustee && TEXT(')') == *strend) - { - PSID pSid = NULL; - - *strend = TEXT('\0'); - trustee++; - - if (!ConvertStringSidToSid(trustee, &pSid)) - pSid = NULL; - - *trusteeForm = TRUSTEE_IS_SID; - return (TCHAR*)pSid; - } - - *trusteeForm = TRUSTEE_IS_NAME; - TCHAR* ret = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - if (ret) lstrcpy(ret, trustee); - return ret; -} - -static PSID ParseSid(TCHAR* trustee) -{ - PSID pSid = NULL; - TCHAR* strend = trustee + lstrlen(trustee) - 1; - - if (TEXT('(') == *trustee && TEXT(')') == *strend) - { - *strend = TEXT('\0'); - ++trustee; - - if (!ConvertStringSidToSid(trustee, &pSid)) pSid = NULL; - } - else - { - DWORD sidLen = 0; - DWORD domLen = 0; - TCHAR* domain = NULL; - SID_NAME_USE use; - - if ((LookupAccountName(NULL, trustee, - NULL, &sidLen, NULL, &domLen, &use) || - ERROR_INSUFFICIENT_BUFFER == GetLastError()) && - NULL != (domain = (TCHAR*)LocalAlloc(domLen*sizeof(TCHAR))) && - NULL != (pSid = (PSID)LocalAlloc(sidLen))) - { - if (!LookupAccountName(NULL, trustee, - pSid, &sidLen, domain, &domLen, &use)) - { - LocalFree(pSid); - pSid = NULL; - } - } - - LocalFree(domain); - } - - return pSid; -} - -/* i know: this function is far to generious in accepting strings. - * but hey: this all is about code size, isn't it? - * so i can live with that pretty well. - */ -static DWORD ParsePermissions(const SchemeType* scheme, TCHAR* str) -{ - DWORD perms = 0; - TCHAR* first, * last; - - for(first = str; *first; first = last) - { - int token; - - while(*first && *first <= TEXT(' ')) ++first; - for(last = first; *last && *last > TEXT(' ') && *last != TEXT('|') && - *last != TEXT('+'); ++last); - if (*last) *last++ = TEXT('\0'); - - token = ParseEnum(scheme->permissionNames, first); - if (token >= 0 && token < scheme->permissionCount) - perms|= scheme->permissionFlags[token]; - } - - return perms; -} - -/***************************************************************************** - SYMBOL TABLES - *****************************************************************************/ - -static const TCHAR* g_filePermissionNames[] = -{ - TEXT("ReadData"), TEXT("WriteData"), TEXT("AppendData"), - TEXT("ReadEA"), TEXT("WriteEA"), TEXT("Execute"), TEXT("ReadAttributes"), TEXT("WriteAttributes"), - TEXT("Delete"), TEXT("ReadControl"), TEXT("WriteDAC"), TEXT("WriteOwner"), TEXT("Synchronize"), - TEXT("FullAccess"), TEXT("GenericRead"), TEXT("GenericWrite"), TEXT("GenericExecute"), NULL -}; - -static const DWORD g_filePermissionFlags[] = -{ - FILE_READ_DATA, FILE_WRITE_DATA, FILE_APPEND_DATA, - FILE_READ_EA, FILE_WRITE_EA, FILE_EXECUTE, FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, - DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, - FILE_ALL_ACCESS, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE -}; - -static const TCHAR* g_directoryPermissionNames[] = -{ - TEXT("ListDirectory"), TEXT("AddFile"), TEXT("AddSubdirectory"), - TEXT("ReadEA"), TEXT("WriteEA"), TEXT("Traverse"), TEXT("DeleteChild"), - TEXT("ReadAttributes"), TEXT("WriteAttributes"), - TEXT("Delete"), TEXT("ReadControl"), TEXT("WriteDAC"), TEXT("WriteOwner"), TEXT("Synchronize"), - TEXT("FullAccess"), TEXT("GenericRead"), TEXT("GenericWrite"), TEXT("GenericExecute"), NULL -}; - -static const DWORD g_directoryPermissionFlags[] = -{ - FILE_LIST_DIRECTORY, FILE_ADD_FILE, FILE_ADD_SUBDIRECTORY, - FILE_READ_EA, FILE_WRITE_EA, FILE_TRAVERSE, FILE_DELETE_CHILD, - FILE_READ_ATTRIBUTES, FILE_WRITE_ATTRIBUTES, - DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, - FILE_ALL_ACCESS, FILE_GENERIC_READ, FILE_GENERIC_WRITE, FILE_GENERIC_EXECUTE -}; - -static const TCHAR* g_registryPermissionNames[] = -{ - TEXT("QueryValue"), TEXT("SetValue"), TEXT("CreateSubKey"), - TEXT("EnumerateSubKeys"), TEXT("Notify"), TEXT("CreateLink"), - TEXT("Delete"), TEXT("ReadControl"), TEXT("WriteDAC"), TEXT("WriteOwner"), TEXT("Synchronize"), - TEXT("GenericRead"), TEXT("GenericWrite"), TEXT("GenericExecute"), TEXT("FullAccess"), NULL -}; - -static const DWORD g_registryPermissionFlags[] = -{ - KEY_QUERY_VALUE, KEY_SET_VALUE, KEY_CREATE_SUB_KEY, - KEY_ENUMERATE_SUB_KEYS, KEY_NOTIFY, KEY_CREATE_LINK, - DELETE, READ_CONTROL, WRITE_DAC, WRITE_OWNER, SYNCHRONIZE, - KEY_READ, KEY_WRITE, KEY_EXECUTE, KEY_ALL_ACCESS -}; - -static const SchemeType g_fileScheme[] = -{ - TEXT("file"), SE_FILE_OBJECT, - OBJECT_INHERIT_ACE, - g_filePermissionNames, - g_filePermissionFlags, - SIZE_OF_ARRAY(g_filePermissionFlags) -}; - -static const SchemeType g_directoryScheme[] = -{ - TEXT("directory"), SE_FILE_OBJECT, - OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, - g_directoryPermissionNames, - g_directoryPermissionFlags, - SIZE_OF_ARRAY(g_directoryPermissionFlags) -}; - -static const SchemeType g_registryScheme[] = -{ - TEXT("registry"), SE_REGISTRY_KEY, - CONTAINER_INHERIT_ACE, - g_registryPermissionNames, - g_registryPermissionFlags, - SIZE_OF_ARRAY(g_registryPermissionFlags) -}; - -static const TCHAR* g_rootKeyNames[] = -{ - TEXT("HKCR"), TEXT("HKCU"), TEXT("HKLM"), TEXT("HKU"), NULL -}; - -static const HKEY g_rootKeyValues[] = -{ - HKEY_CLASSES_ROOT, HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, HKEY_USERS, NULL -}; - -static const TCHAR* g_rootKeyPrefixes[] = -{ - TEXT("CLASSES_ROOT\\"), TEXT("CURRENT_USER\\"), TEXT("MACHINE\\"), TEXT("USERS\\") -}; - -/***************************************************************************** - GENERIC ACL HANDLING - *****************************************************************************/ - -static DWORD MySetNamedSecurityInfo(const SchemeType* scheme, TCHAR* path, SECURITY_INFORMATION si, PSID psidOwner, PSID psidGroup, PACL pDacl, Options* options) -{ - DWORD ret; - - if (scheme->type == SE_REGISTRY_KEY && (g_extra->exec_flags->alter_reg_view & KEY_WOW64_64KEY)) - { - HKEY hKey = NULL; - if ((ret = RegOpenKeyEx(options->hRootKey, path, 0, (si & OWNER_SECURITY_INFORMATION || si & GROUP_SECURITY_INFORMATION ? WRITE_OWNER : 0)|(si & DACL_SECURITY_INFORMATION ? WRITE_DAC : 0)|KEY_WOW64_64KEY, &hKey)) == ERROR_SUCCESS) - { - SECURITY_DESCRIPTOR sd; - InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION); - if (si & OWNER_SECURITY_INFORMATION) - SetSecurityDescriptorOwner(&sd, psidOwner, FALSE); - if (si & GROUP_SECURITY_INFORMATION) - SetSecurityDescriptorGroup(&sd, psidGroup, FALSE); - if (si & DACL_SECURITY_INFORMATION) - SetSecurityDescriptorDacl(&sd, TRUE, pDacl, FALSE); - ret = RegSetKeySecurity(hKey, si, &sd); - RegCloseKey(hKey); - } - } - else - { - ret = SetNamedSecurityInfo(path, scheme->type, si, psidOwner, psidGroup, pDacl, NULL); - } - - return ret; -} - -static DWORD MyGetNamedSecurityInfo(const SchemeType* scheme, TCHAR* path, SECURITY_INFORMATION si, PSECURITY_DESCRIPTOR* ppsd, Options* options) -{ - DWORD ret = 1; - - if (scheme->type == SE_REGISTRY_KEY && (g_extra->exec_flags->alter_reg_view & KEY_WOW64_64KEY)) - { - HKEY hKey = NULL; - if ((ret = RegOpenKeyEx(options->hRootKey, path, 0, READ_CONTROL|KEY_WOW64_64KEY, &hKey)) == ERROR_SUCCESS) - { - DWORD dwSize = 0; - if ((ret = RegGetKeySecurity(hKey, si, NULL, &dwSize)) == ERROR_INSUFFICIENT_BUFFER) - if (NULL != (*ppsd = (PSECURITY_DESCRIPTOR)LocalAlloc(dwSize))) - ret = RegGetKeySecurity(hKey, si, *ppsd, &dwSize); - RegCloseKey(hKey); - } - } - else - { - ret = GetNamedSecurityInfo(path, scheme->type, si, NULL, NULL, NULL, NULL, ppsd); - } - - return ret; -} - -static DWORD ChangeDACL(const SchemeType* scheme, TCHAR* path, DWORD mode, Options* options, TCHAR* param) -{ - TCHAR* trusteeName = NULL; - PSECURITY_DESCRIPTOR psd = NULL; - PSID pSid = NULL; - DWORD trusteeForm = TRUSTEE_IS_NAME; - DWORD permissions = 0; - PACL pOldAcl = NULL; - PACL pNewAcl = NULL; - EXPLICIT_ACCESS access; - BOOL present = TRUE; - BOOL defaulted = FALSE; - - DWORD ret = 1; - - if (popstring(param)) - ABORT("Trustee is missing"); - - if (NULL == (trusteeName = ParseTrustee(param, &trusteeForm))) - ABORT_s("Bad trustee (%s)", param); - - if (popstring(param)) - ABORT("Permission flags are missing"); - - if (0 == (permissions = ParsePermissions(scheme, param))) - ABORT_s("Bad permission flags (%s)", param); - - ret = MyGetNamedSecurityInfo(scheme, path, DACL_SECURITY_INFORMATION, &psd, options); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot read access control list. Error code: %d", ret); - - GetSecurityDescriptorDacl(psd, &present, &pOldAcl, &defaulted); - - BuildExplicitAccessWithName(&access, TEXT(""), permissions, (ACCESS_MODE)mode, scheme->defaultInheritance); - - access.Trustee.TrusteeForm = (TRUSTEE_FORM)trusteeForm; - access.Trustee.ptstrName = trusteeName; - if (options->noInherit) - access.grfInheritance = NO_INHERITANCE; - - ret = SetEntriesInAcl(1, &access, pOldAcl, &pNewAcl); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot build new access control list. Error code: %d", ret); - - ret = MySetNamedSecurityInfo(scheme, path, DACL_SECURITY_INFORMATION, NULL, NULL, pNewAcl, options); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot apply new access control list. Error code: %d", ret); - -cleanup: - LocalFree(pNewAcl); - LocalFree(psd); - LocalFree(trusteeName); - return ret; -} - -static DWORD ChangeInheritance(const SchemeType* scheme, TCHAR* path, Options* options) -{ - PSECURITY_DESCRIPTOR psd = NULL; - PACL pOldAcl = NULL; - BOOL present = TRUE; - BOOL defaulted = FALSE; - - DWORD ret = MyGetNamedSecurityInfo(scheme, path, DACL_SECURITY_INFORMATION, &psd, options); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot read access control list. Error code: %d", ret); - - GetSecurityDescriptorDacl(psd, &present, &pOldAcl, &defaulted); - - ret = MySetNamedSecurityInfo(scheme, path, DACL_SECURITY_INFORMATION | (!options->noInherit ? UNPROTECTED_DACL_SECURITY_INFORMATION : PROTECTED_DACL_SECURITY_INFORMATION), NULL, NULL, pOldAcl, options); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot change access control list inheritance. Error code: %d", ret); - -cleanup: - LocalFree(psd); - return ret; -} - -BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) -{ - TOKEN_PRIVILEGES tp; - LUID luid; - - if (!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) - return FALSE; - - tp.PrivilegeCount = 1; - tp.Privileges[0].Luid = luid; - tp.Privileges[0].Attributes = (bEnablePrivilege ? SE_PRIVILEGE_ENABLED : 0); - - if (!AdjustTokenPrivileges( - hToken, - FALSE, - &tp, - sizeof(TOKEN_PRIVILEGES), - (PTOKEN_PRIVILEGES)NULL, - (PDWORD)NULL)) - return FALSE; - - if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) - return FALSE; - - return TRUE; -} - -static DWORD ChangeOwner(const SchemeType* scheme, TCHAR* path, ChangeMode mode, Options* options, TCHAR* param) -{ - DWORD ret = 1; - PSID pSid = NULL, pSidOwner = NULL, pSidGroup = NULL; - SECURITY_INFORMATION what; - HANDLE hToken; - - if (popstring(param)) - ABORT("Trustee is missing"); - - if (NULL == (pSid = ParseSid(param))) - ABORT_s("Bad trustee (%s)", param); - - switch(mode) - { - case ChangeMode_Owner: - what = OWNER_SECURITY_INFORMATION; - pSidOwner = pSid; - break; - - case ChangeMode_Group: - what = GROUP_SECURITY_INFORMATION; - pSidGroup = pSid; - break; - - default: - ABORT_d("Bug: Unsupported change mode: %d", mode); - } - - if (!OpenProcessToken(GetCurrentProcess(), - TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) - ABORT_d("Cannot open process token. Error code: %d", GetLastError()); - - // You only need WRITE_OWNER to change the owner of the object to yourself - // blogs.msdn.com/b/oldnewthing/archive/2005/08/18/453054.aspx - BOOL privRe = SetPrivilege(hToken, SE_RESTORE_NAME, TRUE); - BOOL privTO = SetPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, TRUE); - - ret = MySetNamedSecurityInfo(scheme, path, what, pSidOwner, pSidGroup, NULL, options); - - if (privRe) SetPrivilege(hToken, SE_RESTORE_NAME, FALSE); - if (privTO) SetPrivilege(hToken, SE_TAKE_OWNERSHIP_NAME, FALSE); - - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot apply new ownership. Error code: %d", ret); - -cleanup: - LocalFree(pSid); - CloseHandle(hToken); - return ret; -} - -static DWORD GetOwner(const SchemeType* scheme, TCHAR* path, ChangeMode mode, TCHAR* owner, TCHAR* domain, Options* options) -{ - SECURITY_INFORMATION what; - PSECURITY_DESCRIPTOR psd = NULL; - PSID pSid = NULL, pSidGroup = NULL, pSidOwner = NULL; - SID_NAME_USE eUse = SidTypeUnknown; - DWORD dwOwner = g_string_size, dwDomain = g_string_size; - BOOL defaulted = FALSE; - DWORD ret = 1; - - switch(mode) - { - case ChangeMode_Owner: - what = OWNER_SECURITY_INFORMATION; - break; - - case ChangeMode_Group: - what = GROUP_SECURITY_INFORMATION; - break; - - default: - ABORT_d("Bug: Unsupported change mode: %d", mode); - } - - ret = MyGetNamedSecurityInfo(scheme, path, what, &psd, options); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot get current ownership. Error code: %d", ret); - - if (mode == OWNER_SECURITY_INFORMATION) - GetSecurityDescriptorOwner(psd, &pSidOwner, &defaulted); - else - GetSecurityDescriptorGroup(psd, &pSidGroup, &defaulted); - - if (!LookupAccountSid(NULL, (pSidOwner ? pSidOwner : pSidGroup), - owner, &dwOwner, domain, &dwDomain, &eUse)) - ABORT_d("Cannot look up owner. Error code: %d", GetLastError()); - -cleanup: - LocalFree(psd); - return ret; -} - -static DWORD ClearACL(const SchemeType* scheme, TCHAR* path, Options* options, TCHAR* param) -{ - TCHAR* trusteeName = NULL; - PSID pSid = NULL; - DWORD trusteeForm = TRUSTEE_IS_NAME; - DWORD permissions = 0; - PACL pNewAcl = NULL; - EXPLICIT_ACCESS access; - - DWORD ret = 1; - - if (popstring(param)) - ABORT("Trustee is missing"); - - if (NULL == (trusteeName = ParseTrustee(param, &trusteeForm))) - ABORT_s("Bad trustee (%s)", param); - - if (popstring(param)) - ABORT("Permission flags are missing"); - - if (0 == (permissions = ParsePermissions(scheme, param))) - ABORT_s("Bad permission flags (%s)", param); - - BuildExplicitAccessWithName(&access, TEXT(""), permissions, SET_ACCESS, scheme->defaultInheritance); - - access.Trustee.TrusteeForm = (TRUSTEE_FORM)trusteeForm; - access.Trustee.ptstrName = trusteeName; - if (options->noInherit) - access.grfInheritance = NO_INHERITANCE; - - ret = SetEntriesInAcl(1, &access, NULL, &pNewAcl); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot build new access control list. Error code: %d", ret); - - ret = MySetNamedSecurityInfo(scheme, path, DACL_SECURITY_INFORMATION, NULL, NULL, pNewAcl, options); - if (ret != ERROR_SUCCESS) - ABORT_d("Cannot change access control list inheritance. Error code: %d", ret); - -cleanup: - LocalFree(pNewAcl); - LocalFree(trusteeName); - return ret; -} - -/***************************************************************************** - FILESYSTEM BASED ACL HANDLING - *****************************************************************************/ - -static const SchemeType* PopFileArgs(TCHAR* path, Options* options) -{ - if (popstring(path) == 0) - { - DWORD attr; - options->noInherit = FALSE; - if (lstrcmpi(path, TEXT("/noinherit")) == 0) - { - options->noInherit = TRUE; - popstring(path); - } - attr = GetFileAttributes(path); - - if (INVALID_FILE_ATTRIBUTES != attr) - return FILE_ATTRIBUTE_DIRECTORY & attr - ? g_directoryScheme - : g_fileScheme; - else - ABORT("Invalid filesystem path missing"); - } - else - ABORT("Filesystem path missing"); - -cleanup: - return NULL; -} - -static void ChangeFileInheritance(BOOL inherit) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - const SchemeType* scheme; - Options options; - - if (NULL != (scheme = PopFileArgs(path, &options))) - { - options.noInherit = !inherit; - if (0 == (ret = ChangeInheritance(scheme, path, &options))) - pushstring(TEXT("ok")); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -static void ChangeFileDACL(DWORD mode) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (param) - { - const SchemeType* scheme; - Options options; - - if (NULL != (scheme = PopFileArgs(path, &options))) - if (0 == (ret = ChangeDACL(scheme, path, mode, &options, param))) - pushstring(TEXT("ok")); - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -static void ChangeFileOwner(ChangeMode mode) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (param) - { - const SchemeType* scheme; - Options options; - - if (NULL != (scheme = PopFileArgs(path, &options))) - if (0 == (ret = ChangeOwner(scheme, path, mode, &options, param))) - pushstring(TEXT("ok")); - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -static void PushFileOwner(ChangeMode mode) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* owner = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (owner) - { - TCHAR* domain = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (domain) - { - const SchemeType* scheme; - Options options; - - if (NULL != (scheme = PopFileArgs(path, &options))) - { - if (0 == (ret = GetOwner(scheme, path, mode, owner, domain, &options))) - pushstring(owner); - } - - LocalFree(domain); - } - - LocalFree(owner); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -/***************************************************************************** - REGISTRY BASED ACL HANDLING - *****************************************************************************/ - -static BOOL PopRegKeyArgs(TCHAR* path, Options* options, TCHAR* param) -{ - int iRootKey; - BOOL success = FALSE; - - if (popstring(param) == 0) - { - options->noInherit = FALSE; - if (lstrcmpi(param, TEXT("/noinherit")) == 0) - { - options->noInherit = TRUE; - popstring(param); - } - } - else - ABORT("Root key name missing"); - - iRootKey = ParseEnum(g_rootKeyNames, param); - if (!ARRAY_CONTAINS(g_rootKeyPrefixes, iRootKey)) - ABORT_s("Bad root key name (%s)", param); - - if (popstring(param) != 0) - ABORT("Registry key name missing"); - - if (g_extra->exec_flags->alter_reg_view & KEY_WOW64_64KEY) - { - options->hRootKey = g_rootKeyValues[iRootKey]; - lstrcpy(path, param); - } - else - { - lstrcpy(path, g_rootKeyPrefixes[iRootKey]); - lstrcat(path, param); - } - - success = TRUE; - -cleanup: - return success; -} - -static void ChangeRegKeyInheritance(BOOL inherit) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (param) - { - Options options; - - if (PopRegKeyArgs(path, &options, param)) - { - options.noInherit = !inherit; - if (0 == (ret = ChangeInheritance(g_registryScheme, path, &options))) - pushstring(TEXT("ok")); - } - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -static void ChangeRegKeyDACL(DWORD mode) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (param) - { - Options options; - - if (PopRegKeyArgs(path, &options, param)) - if (0 == (ret = ChangeDACL(g_registryScheme, path, mode, &options, param))) - pushstring(TEXT("ok")); - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -static void ChangeRegKeyOwner(ChangeMode mode) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (param) - { - Options options; - - if (PopRegKeyArgs(path, &options, param)) - if (0 == (ret = ChangeOwner(g_registryScheme, path, mode, &options, param))) - pushstring(TEXT("ok")); - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -static void PushRegKeyOwner(ChangeMode mode) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* owner = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (owner) - { - TCHAR* domain = (TCHAR*)LocalAlloc(g_string_size*sizeof(TCHAR)); - - if (domain) - { - Options options; - - if (PopRegKeyArgs(path, &options, owner)) - if (0 == (ret = GetOwner(g_registryScheme, path, mode, owner, domain, &options))) - pushstring(owner); - - LocalFree(domain); - } - - LocalFree(owner); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} - -/***************************************************************************** - PUBLIC FILE RELATED FUNCTIONS - *****************************************************************************/ - -PUBLIC_FUNCTION(EnableFileInheritance) - ChangeFileInheritance(TRUE); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(DisableFileInheritance) - ChangeFileInheritance(FALSE); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GrantOnFile) - ChangeFileDACL(GRANT_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SetOnFile) - ChangeFileDACL(SET_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(DenyOnFile) - ChangeFileDACL(DENY_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(RevokeOnFile) - ChangeFileDACL(REVOKE_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SetFileOwner) - ChangeFileOwner(ChangeMode_Owner); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SetFileGroup) - ChangeFileOwner(ChangeMode_Group); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GetFileOwner) - PushFileOwner(ChangeMode_Owner); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GetFileGroup) - PushFileOwner(ChangeMode_Group); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(ClearOnFile) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)); - - if (param) - { - const SchemeType* scheme; - Options options; - - if (NULL != (scheme = PopFileArgs(path, &options))) - if (0 == (ret = ClearACL(scheme, path, &options, param))) - pushstring(TEXT("ok")); - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} -PUBLIC_FUNCTION_END - -/***************************************************************************** - PUBLIC REGISTRY RELATED FUNCTIONS - *****************************************************************************/ - -PUBLIC_FUNCTION(EnableRegKeyInheritance) - ChangeRegKeyInheritance(TRUE); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(DisableRegKeyInheritance) - ChangeRegKeyInheritance(FALSE); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GrantOnRegKey) - ChangeRegKeyDACL(GRANT_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SetOnRegKey) - ChangeRegKeyDACL(SET_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(DenyOnRegKey) - ChangeRegKeyDACL(DENY_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(RevokeOnRegKey) - ChangeRegKeyDACL(REVOKE_ACCESS); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SetRegKeyOwner) - ChangeRegKeyOwner(ChangeMode_Owner); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SetRegKeyGroup) - ChangeRegKeyOwner(ChangeMode_Group); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GetRegKeyOwner) - PushRegKeyOwner(ChangeMode_Owner); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GetRegKeyGroup) - PushRegKeyOwner(ChangeMode_Group); -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(ClearOnRegKey) -{ - DWORD ret = 1; - TCHAR* path = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)); - - if (path) - { - TCHAR* param = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)); - - if (param) - { - Options options; - - if (PopRegKeyArgs(path, &options, param)) - if (0 == (ret = ClearACL(g_registryScheme, path, &options, param))) - pushstring(TEXT("ok")); - - LocalFree(param); - } - - LocalFree(path); - } - - if (ret) pushstring(TEXT("error")); -} -PUBLIC_FUNCTION_END - -/***************************************************************************** - OTHER ACCOUNT RELATED FUNCTIONS - *****************************************************************************/ - -PUBLIC_FUNCTION(NameToSid) -{ - DWORD ret = 1; - TCHAR *param = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)), *retstr = ERRSTR_OOM; - - if (param) - { - if (popstring(param) == 0) - { - DWORD dwSid = 0; - DWORD dwDomain = 0; - SID_NAME_USE eUse; - LookupAccountName(NULL, param, NULL, &dwSid, NULL, &dwDomain, &eUse); - - if (dwSid > 0) - { - PSID pSid = (PSID)LocalAlloc(dwSid); - if (pSid) - { - TCHAR* domain = (TCHAR*)LocalAlloc(dwDomain*sizeof(TCHAR)); - if (domain) - { - retstr = param; - if (!LookupAccountName(NULL, param, pSid, &dwSid, domain, &dwDomain, &eUse) || !ConvertSidToStringSidNoAlloc(pSid, param)) - wsprintf(param, TEXT("Cannot look up name. Error code: %d"), GetLastError()); - else - ret = 0; - LocalFree(domain); - } - LocalFree(pSid); - } - } - } - } - pushstring(retstr); - LocalFree(param); - - if (ret) pushstring(TEXT("error")); -} -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(SidToName) -{ - DWORD ret = 1; - TCHAR *param = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)); - - if (param) - { - DWORD dwName = string_size; - TCHAR* name = (TCHAR*)LocalAlloc(dwName*sizeof(TCHAR)); - - if (name) - { - DWORD dwDomain = string_size; - TCHAR* domain = (TCHAR*)LocalAlloc(dwDomain*sizeof(TCHAR)); - - if (domain) - { - if (popstring(param) == 0) - { - PSID pSid = NULL; - SID_NAME_USE eUse; - if (!ConvertStringSidToSid(param, &pSid) || !LookupAccountSid(NULL, pSid, name, &dwName, domain, &dwDomain, &eUse)) - { - wsprintf(param, TEXT("Cannot look up SID. Error code: %d"), GetLastError()); - pushstring(param); // BUGBUG: What if LocalAlloc fails and we never get here? - } - else - { - pushstring(name); - pushstring(domain); - ret = 0; - } - LocalFree(pSid); - } - LocalFree(domain); - } - LocalFree(name); - } - LocalFree(param); - } - - if (ret) pushstring(TEXT("error")); -} -PUBLIC_FUNCTION_END - -PUBLIC_FUNCTION(GetCurrentUserName) -{ - TCHAR *name = (TCHAR*)LocalAlloc(string_size*sizeof(TCHAR)), *retstr = TEXT("error"); - DWORD dwName = string_size; - if (name && GetUserName(name, &dwName)) retstr = name; - pushstring(retstr); - LocalFree(name); -} -PUBLIC_FUNCTION_END - -#ifdef _VC_NODEFAULTLIB -#define DllMain _DllMainCRTStartup -#endif -EXTERN_C BOOL WINAPI DllMain(HINSTANCE hInst, ULONG ul_reason_for_call, LPVOID lpReserved) -{ - g_hInstance = (HINSTANCE)hInst; - return TRUE; -} \ No newline at end of file diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.rc b/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.rc deleted file mode 100644 index adb653e84..000000000 Binary files a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.rc and /dev/null differ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.sln b/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.sln deleted file mode 100644 index 3f286057c..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.sln +++ /dev/null @@ -1,22 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AccessControl", "AccessControl.vcxproj", "{FA391322-C61F-4CC4-A421-432A0C3D5342}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Release Unicode|Win32 = Release Unicode|Win32 - Release|Win32 = Release|Win32 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FA391322-C61F-4CC4-A421-432A0C3D5342}.Debug|Win32.ActiveCfg = Debug|Win32 - {FA391322-C61F-4CC4-A421-432A0C3D5342}.Debug|Win32.Build.0 = Debug|Win32 - {FA391322-C61F-4CC4-A421-432A0C3D5342}.Release Unicode|Win32.ActiveCfg = Release Unicode|Win32 - {FA391322-C61F-4CC4-A421-432A0C3D5342}.Release Unicode|Win32.Build.0 = Release Unicode|Win32 - {FA391322-C61F-4CC4-A421-432A0C3D5342}.Release|Win32.ActiveCfg = Release|Win32 - {FA391322-C61F-4CC4-A421-432A0C3D5342}.Release|Win32.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.vcxproj b/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.vcxproj deleted file mode 100644 index 4326673e2..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/AccessControl.vcxproj +++ /dev/null @@ -1,174 +0,0 @@ - - - - - Debug - Win32 - - - Release Unicode - Win32 - - - Release - Win32 - - - - {FA391322-C61F-4CC4-A421-432A0C3D5342} - AccessControl - - - - DynamicLibrary - Unicode - - - DynamicLibrary - MultiByte - - - DynamicLibrary - false - MultiByte - - - - - - - - - - - - - - - - - - - <_ProjectFileVersion>10.0.40219.1 - $(SolutionDir)..\..\Plugins\ - true - false - false - false - $(SolutionDir)..\..\Unicode\Plugins\ - false - false - $(SolutionDir)..\..\Plugins\ - - - - _DEBUG;%(PreprocessorDefinitions) - true - true - Win32 - .\Debug/AccessControl.tlb - - - - - Disabled - _WIN32_WINNT=0x0500;%(PreprocessorDefinitions) - EnableFastChecks - MultiThreadedDebug - - - .\Debug/AccessControl.pch - Level3 - true - EditAndContinue - - - _DEBUG;%(PreprocessorDefinitions) - 0x0409 - - - true - true - MachineX86 - nsis_ansi/pluginapi.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) - libc.lib - - - - - MinSpace - OnlyExplicitInline - Neither - false - _WIN32_WINNT=0x0500;%(PreprocessorDefinitions) - true - Sync - MultiThreaded - false - true - false - - - .\Release/AccessControl.pch - Level3 - - - - - advapi32.lib;nsis_ansi/pluginapi.lib;%(AdditionalDependencies) - true - true - false - Windows - true - true - DllMain - MachineX86 - - - - - MinSpace - OnlyExplicitInline - Neither - false - _WIN32_WINNT=0x0500;%(PreprocessorDefinitions) - true - Sync - MultiThreaded - false - true - false - - - .\Release/AccessControl.pch - Level3 - - - - - advapi32.lib;nsis_unicode/pluginapi.lib;%(AdditionalDependencies) - true - true - false - Windows - true - true - DllMain - MachineX86 - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/api.h b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/api.h deleted file mode 100644 index e22c72ccb..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/api.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * apih - * - * This file is a part of NSIS. - * - * Copyright (C) 1999-2009 Nullsoft and Contributors - * - * Licensed under the zlib/libpng license (the "License"); - * you may not use this file except in compliance with the License. - * - * Licence details can be found in the file COPYING. - * - * This software is provided 'as-is', without any express or implied - * warranty. - */ - -#ifndef _NSIS_EXEHEAD_API_H_ -#define _NSIS_EXEHEAD_API_H_ - -// Starting with NSIS 2.42, you can check the version of the plugin API in exec_flags->plugin_api_version -// The format is 0xXXXXYYYY where X is the major version and Y is the minor version (MAKELONG(y,x)) -// When doing version checks, always remember to use >=, ex: if (pX->exec_flags->plugin_api_version >= NSISPIAPIVER_1_0) {} - -#define NSISPIAPIVER_1_0 0x00010000 -#define NSISPIAPIVER_CURR NSISPIAPIVER_1_0 - -// NSIS Plug-In Callback Messages -enum NSPIM -{ - NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup - NSPIM_GUIUNLOAD, // Called after .onGUIEnd -}; - -// Prototype for callbacks registered with extra_parameters->RegisterPluginCallback() -// Return NULL for unknown messages -// Should always be __cdecl for future expansion possibilities -typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); - -// extra_parameters data structures containing other interesting stuff -// but the stack, variables and HWND passed on to plug-ins. -typedef struct -{ - int autoclose; - int all_user_var; - int exec_error; - int abort; - int exec_reboot; // NSIS_SUPPORT_REBOOT - int reboot_called; // NSIS_SUPPORT_REBOOT - int XXX_cur_insttype; // depreacted - int plugin_api_version; // see NSISPIAPIVER_CURR - // used to be XXX_insttype_changed - int silent; // NSIS_CONFIG_SILENT_SUPPORT - int instdir_error; - int rtl; - int errlvl; - int alter_reg_view; - int status_update; -} exec_flags_t; - -#ifndef NSISCALL -# define NSISCALL __stdcall -#endif - -typedef struct { - exec_flags_t *exec_flags; - int (NSISCALL *ExecuteCodeSegment)(int, HWND); - void (NSISCALL *validate_filename)(char *); - int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors -} extra_parameters; - -// Definitions for page showing plug-ins -// See Ui.c to understand better how they're used - -// sent to the outer window to tell it to go to the next inner window -#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) - -// custom pages should send this message to let NSIS know they're ready -#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) - -// sent as wParam with WM_NOTIFY_OUTER_NEXT when user cancels - heed its warning -#define NOTIFY_BYE_BYE 'x' - -#endif /* _PLUGIN_H_ */ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/pluginapi.h b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/pluginapi.h deleted file mode 100644 index a63202671..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/pluginapi.h +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef ___NSIS_PLUGIN__H___ -#define ___NSIS_PLUGIN__H___ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "api.h" - -#ifndef NSISCALL -# define NSISCALL __stdcall -#endif - -#define EXDLL_INIT() { \ - g_stringsize=string_size; \ - g_stacktop=stacktop; \ - g_variables=variables; } - -typedef struct _stack_t { - struct _stack_t *next; - char text[1]; // this should be the length of string_size -} stack_t; - -enum -{ -INST_0, // $0 -INST_1, // $1 -INST_2, // $2 -INST_3, // $3 -INST_4, // $4 -INST_5, // $5 -INST_6, // $6 -INST_7, // $7 -INST_8, // $8 -INST_9, // $9 -INST_R0, // $R0 -INST_R1, // $R1 -INST_R2, // $R2 -INST_R3, // $R3 -INST_R4, // $R4 -INST_R5, // $R5 -INST_R6, // $R6 -INST_R7, // $R7 -INST_R8, // $R8 -INST_R9, // $R9 -INST_CMDLINE, // $CMDLINE -INST_INSTDIR, // $INSTDIR -INST_OUTDIR, // $OUTDIR -INST_EXEDIR, // $EXEDIR -INST_LANG, // $LANGUAGE -__INST_LAST -}; - -extern unsigned int g_stringsize; -extern stack_t **g_stacktop; -extern char *g_variables; - -int NSISCALL popstring(char *str); // 0 on success, 1 on empty stack -int NSISCALL popstringn(char *str, int maxlen); // with length limit, pass 0 for g_stringsize -int NSISCALL popint(); // pops an integer -int NSISCALL popint_or(); // with support for or'ing (2|4|8) -int NSISCALL myatoi(const char *s); // converts a string to an integer -unsigned NSISCALL myatou(const char *s); // converts a string to an unsigned integer, decimal only -int NSISCALL myatoi_or(const char *s); // with support for or'ing (2|4|8) -void NSISCALL pushstring(const char *str); -void NSISCALL pushint(int value); -char * NSISCALL getuservariable(const int varnum); -void NSISCALL setuservariable(const int varnum, const char *var); - -#ifdef __cplusplus -} -#endif - -#endif//!___NSIS_PLUGIN__H___ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/pluginapi.lib b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/pluginapi.lib deleted file mode 100644 index ea64b80d8..000000000 Binary files a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_ansi/pluginapi.lib and /dev/null differ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/api.h b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/api.h deleted file mode 100644 index 9d7b20797..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/api.h +++ /dev/null @@ -1,83 +0,0 @@ -/* - * apih - * - * This file is a part of NSIS. - * - * Copyright (C) 1999-2009 Nullsoft and Contributors - * - * Licensed under the zlib/libpng license (the "License"); - * you may not use this file except in compliance with the License. - * - * Licence details can be found in the file COPYING. - * - * This software is provided 'as-is', without any express or implied - * warranty. - */ - -#ifndef _NSIS_EXEHEAD_API_H_ -#define _NSIS_EXEHEAD_API_H_ - -// Starting with NSIS 2.42, you can check the version of the plugin API in exec_flags->plugin_api_version -// The format is 0xXXXXYYYY where X is the major version and Y is the minor version (MAKELONG(y,x)) -// When doing version checks, always remember to use >=, ex: if (pX->exec_flags->plugin_api_version >= NSISPIAPIVER_1_0) {} - -#define NSISPIAPIVER_1_0 0x00010000 -#define NSISPIAPIVER_CURR NSISPIAPIVER_1_0 - -// NSIS Plug-In Callback Messages -enum NSPIM -{ - NSPIM_UNLOAD, // This is the last message a plugin gets, do final cleanup - NSPIM_GUIUNLOAD, // Called after .onGUIEnd -}; - -// Prototype for callbacks registered with extra_parameters->RegisterPluginCallback() -// Return NULL for unknown messages -// Should always be __cdecl for future expansion possibilities -typedef UINT_PTR (*NSISPLUGINCALLBACK)(enum NSPIM); - -// extra_parameters data structures containing other interesting stuff -// but the stack, variables and HWND passed on to plug-ins. -typedef struct -{ - int autoclose; - int all_user_var; - int exec_error; - int abort; - int exec_reboot; // NSIS_SUPPORT_REBOOT - int reboot_called; // NSIS_SUPPORT_REBOOT - int XXX_cur_insttype; // depreacted - int plugin_api_version; // see NSISPIAPIVER_CURR - // used to be XXX_insttype_changed - int silent; // NSIS_CONFIG_SILENT_SUPPORT - int instdir_error; - int rtl; - int errlvl; - int alter_reg_view; - int status_update; -} exec_flags_t; - -#ifndef NSISCALL -# define NSISCALL __stdcall -#endif - -typedef struct { - exec_flags_t *exec_flags; - int (NSISCALL *ExecuteCodeSegment)(int, HWND); - void (NSISCALL *validate_filename)(TCHAR *); - int (NSISCALL *RegisterPluginCallback)(HMODULE, NSISPLUGINCALLBACK); // returns 0 on success, 1 if already registered and < 0 on errors -} extra_parameters; - -// Definitions for page showing plug-ins -// See Ui.c to understand better how they're used - -// sent to the outer window to tell it to go to the next inner window -#define WM_NOTIFY_OUTER_NEXT (WM_USER+0x8) - -// custom pages should send this message to let NSIS know they're ready -#define WM_NOTIFY_CUSTOM_READY (WM_USER+0xd) - -// sent as wParam with WM_NOTIFY_OUTER_NEXT when user cancels - heed its warning -#define NOTIFY_BYE_BYE 'x' - -#endif /* _PLUGIN_H_ */ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/nsis_tchar.h b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/nsis_tchar.h deleted file mode 100644 index 92025ccc5..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/nsis_tchar.h +++ /dev/null @@ -1,214 +0,0 @@ -/* - * nsis_tchar.h - * - * This file is a part of NSIS. - * - * Copyright (C) 1999-2007 Nullsoft and Contributors - * - * This software is provided 'as-is', without any express or implied - * warranty. - * - * For Unicode support by Jim Park -- 08/30/2007 - */ - -// Jim Park: Only those we use are listed here. - -#pragma once - -#ifdef _UNICODE - -#ifndef _T -#define __T(x) L ## x -#define _T(x) __T(x) -#define _TEXT(x) __T(x) -#endif -typedef wchar_t TCHAR; -typedef wchar_t _TUCHAR; - -// program -#define _tmain wmain -#define _tWinMain wWinMain -#define _tenviron _wenviron -#define __targv __wargv - -// printfs -#define _ftprintf fwprintf -#define _sntprintf _snwprintf -#define _stprintf _swprintf -#define _tprintf wprintf -#define _vftprintf vfwprintf -#define _vsntprintf _vsnwprintf -#define _vstprintf _vswprintf - -// scanfs -#define _tscanf wscanf -#define _stscanf swscanf - -// string manipulations -#define _tcscat wcscat -#define _tcschr wcschr -#define _tcsclen wcslen -#define _tcscpy wcscpy -#define _tcsdup _wcsdup -#define _tcslen wcslen -#define _tcsnccpy wcsncpy -#define _tcsncpy wcsncpy -#define _tcsrchr wcsrchr -#define _tcsstr wcsstr -#define _tcstok wcstok - -// string comparisons -#define _tcscmp wcscmp -#define _tcsicmp _wcsicmp -#define _tcsncicmp _wcsnicmp -#define _tcsncmp wcsncmp -#define _tcsnicmp _wcsnicmp - -// upper / lower -#define _tcslwr _wcslwr -#define _tcsupr _wcsupr -#define _totlower towlower -#define _totupper towupper - -// conversions to numbers -#define _tcstoi64 _wcstoi64 -#define _tcstol wcstol -#define _tcstoul wcstoul -#define _tstof _wtof -#define _tstoi _wtoi -#define _tstoi64 _wtoi64 -#define _ttoi _wtoi -#define _ttoi64 _wtoi64 -#define _ttol _wtol - -// conversion from numbers to strings -#define _itot _itow -#define _ltot _ltow -#define _i64tot _i64tow -#define _ui64tot _ui64tow - -// file manipulations -#define _tfopen _wfopen -#define _topen _wopen -#define _tremove _wremove -#define _tunlink _wunlink - -// reading and writing to i/o -#define _fgettc fgetwc -#define _fgetts fgetws -#define _fputts fputws -#define _gettchar getwchar - -// directory -#define _tchdir _wchdir - -// environment -#define _tgetenv _wgetenv -#define _tsystem _wsystem - -// time -#define _tcsftime wcsftime - -#else // ANSI - -#ifndef _T -#define _T(x) x -#define _TEXT(x) x -#endif -typedef char TCHAR; -typedef unsigned char _TUCHAR; - -// program -#define _tmain main -#define _tWinMain WinMain -#define _tenviron environ -#define __targv __argv - -// printfs -#define _ftprintf fprintf -#define _sntprintf _snprintf -#define _stprintf sprintf -#define _tprintf printf -#define _vftprintf vfprintf -#define _vsntprintf _vsnprintf -#define _vstprintf vsprintf - -// scanfs -#define _tscanf scanf -#define _stscanf sscanf - -// string manipulations -#define _tcscat strcat -#define _tcschr strchr -#define _tcsclen strlen -#define _tcscnlen strnlen -#define _tcscpy strcpy -#define _tcsdup _strdup -#define _tcslen strlen -#define _tcsnccpy strncpy -#define _tcsrchr strrchr -#define _tcsstr strstr -#define _tcstok strtok - -// string comparisons -#define _tcscmp strcmp -#define _tcsicmp _stricmp -#define _tcsncmp strncmp -#define _tcsncicmp _strnicmp -#define _tcsnicmp _strnicmp - -// upper / lower -#define _tcslwr _strlwr -#define _tcsupr _strupr - -#define _totupper toupper -#define _totlower tolower - -// conversions to numbers -#define _tcstol strtol -#define _tcstoul strtoul -#define _tstof atof -#define _tstoi atoi -#define _tstoi64 _atoi64 -#define _tstoi64 _atoi64 -#define _ttoi atoi -#define _ttoi64 _atoi64 -#define _ttol atol - -// conversion from numbers to strings -#define _i64tot _i64toa -#define _itot _itoa -#define _ltot _ltoa -#define _ui64tot _ui64toa - -// file manipulations -#define _tfopen fopen -#define _topen _open -#define _tremove remove -#define _tunlink _unlink - -// reading and writing to i/o -#define _fgettc fgetc -#define _fgetts fgets -#define _fputts fputs -#define _gettchar getchar - -// directory -#define _tchdir _chdir - -// environment -#define _tgetenv getenv -#define _tsystem system - -// time -#define _tcsftime strftime - -#endif - -// is functions (the same in Unicode / ANSI) -#define _istgraph isgraph -#define _istascii __isascii - -#define __TFILE__ _T(__FILE__) -#define __TDATE__ _T(__DATE__) -#define __TTIME__ _T(__TIME__) diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/pluginapi.h b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/pluginapi.h deleted file mode 100644 index b9bfee91c..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/pluginapi.h +++ /dev/null @@ -1,101 +0,0 @@ -#ifndef ___NSIS_PLUGIN__H___ -#define ___NSIS_PLUGIN__H___ - -#ifdef __cplusplus -extern "C" { -#endif - -#include "api.h" -#include "nsis_tchar.h" - -#ifndef NSISCALL -# define NSISCALL __stdcall -#endif - -#define EXDLL_INIT() { \ - g_stringsize=string_size; \ - g_stacktop=stacktop; \ - g_variables=variables; } - -typedef struct _stack_t { - struct _stack_t *next; - TCHAR text[1]; // this should be the length of string_size -} stack_t; - -enum -{ -INST_0, // $0 -INST_1, // $1 -INST_2, // $2 -INST_3, // $3 -INST_4, // $4 -INST_5, // $5 -INST_6, // $6 -INST_7, // $7 -INST_8, // $8 -INST_9, // $9 -INST_R0, // $R0 -INST_R1, // $R1 -INST_R2, // $R2 -INST_R3, // $R3 -INST_R4, // $R4 -INST_R5, // $R5 -INST_R6, // $R6 -INST_R7, // $R7 -INST_R8, // $R8 -INST_R9, // $R9 -INST_CMDLINE, // $CMDLINE -INST_INSTDIR, // $INSTDIR -INST_OUTDIR, // $OUTDIR -INST_EXEDIR, // $EXEDIR -INST_LANG, // $LANGUAGE -__INST_LAST -}; - -extern unsigned int g_stringsize; -extern stack_t **g_stacktop; -extern TCHAR *g_variables; - -int NSISCALL popstring(TCHAR *str); // 0 on success, 1 on empty stack -int NSISCALL popstringn(TCHAR *str, int maxlen); // with length limit, pass 0 for g_stringsize -int NSISCALL popint(); // pops an integer -int NSISCALL popint_or(); // with support for or'ing (2|4|8) -int NSISCALL myatoi(const TCHAR *s); // converts a string to an integer -unsigned NSISCALL myatou(const TCHAR *s); // converts a string to an unsigned integer, decimal only -int NSISCALL myatoi_or(const TCHAR *s); // with support for or'ing (2|4|8) -void NSISCALL pushstring(const TCHAR *str); -void NSISCALL pushint(int value); -TCHAR * NSISCALL getuservariable(const int varnum); -void NSISCALL setuservariable(const int varnum, const TCHAR *var); - -#ifdef _UNICODE -#define PopStringW(x) popstring(x) -#define PushStringW(x) pushstring(x) -#define SetUserVariableW(x,y) setuservariable(x,y) - -int NSISCALL PopStringA(char* ansiStr); -void NSISCALL PushStringA(const char* ansiStr); -void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr); -void NSISCALL GetUserVariableA(const int varnum, char* ansiStr); -void NSISCALL SetUserVariableA(const int varnum, const char* ansiStr); - -#else -// ANSI defs - -#define PopStringA(x) popstring(x) -#define PushStringA(x) pushstring(x) -#define SetUserVariableA(x,y) setuservariable(x,y) - -int NSISCALL PopStringW(wchar_t* wideStr); -void NSISCALL PushStringW(wchar_t* wideStr); -void NSISCALL GetUserVariableW(const int varnum, wchar_t* wideStr); -void NSISCALL GetUserVariableA(const int varnum, char* ansiStr); -void NSISCALL SetUserVariableW(const int varnum, const wchar_t* wideStr); - -#endif - -#ifdef __cplusplus -} -#endif - -#endif//!___NSIS_PLUGIN__H___ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/pluginapi.lib b/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/pluginapi.lib deleted file mode 100644 index 5a208423f..000000000 Binary files a/windows-dependencies/AccessControl/Contrib/AccessControl/nsis_unicode/pluginapi.lib and /dev/null differ diff --git a/windows-dependencies/AccessControl/Contrib/AccessControl/resource.h b/windows-dependencies/AccessControl/Contrib/AccessControl/resource.h deleted file mode 100644 index 1d3560757..000000000 --- a/windows-dependencies/AccessControl/Contrib/AccessControl/resource.h +++ /dev/null @@ -1,14 +0,0 @@ -//{{NO_DEPENDENCIES}} -// Microsoft Visual C++ generated include file. -// Used by AccessControl.rc - -// Next default values for new objects -// -#ifdef APSTUDIO_INVOKED -#ifndef APSTUDIO_READONLY_SYMBOLS -#define _APS_NEXT_RESOURCE_VALUE 101 -#define _APS_NEXT_COMMAND_VALUE 40001 -#define _APS_NEXT_CONTROL_VALUE 1001 -#define _APS_NEXT_SYMED_VALUE 101 -#endif -#endif diff --git a/windows-dependencies/AccessControl/Docs/AccessControl/AccessControl.txt b/windows-dependencies/AccessControl/Docs/AccessControl/AccessControl.txt deleted file mode 100644 index b8e0a4d3d..000000000 --- a/windows-dependencies/AccessControl/Docs/AccessControl/AccessControl.txt +++ /dev/null @@ -1,253 +0,0 @@ -ACCESS CONTROL PLUGIN ---------------------- - -Written by Mathias Hasselmann -Modifications by: -* Afrow UK -* AndersK - -The AccessControl plugin for NSIS provides a set of functions related -Windows NT access control list (ACL) management. - - -MODIFICATIONS -------------- - -v1.0.8.1 - 7th July 2014 - AndersK -* Don't require SE_RESTORE_NAME and SE_TAKE_OWNERSHIP_NAME when changing owner. -* Fixed broken return value when trustee parsing failed - -v1.0.8.0 - 24th March 2014 - AndersK -* Added basic String SID parsing in the emulated ConvertStringSidToSid (Broken in v1.0.6) -* Fixed WinNT4 and Win95 support? (Unicode DLL will not load on Win95 but will probably load on Win98) -* Fixed leaks from ParseSid and ConvertSidToStringSid -* NameToSid and SidToName now pushes "error" and error details -* Better GetCurrentUserName error handling (Still returns the problematic "error" string) - -v1.0.7.0 - 25th February 2012 - Afrow UK -* Fixed DisableFileInheritance (broken in v1.0.5.0). - -v1.0.6.0 - 26th January 2012 - Afrow UK -* Wrote replacements for ConvertSidToStringSid/ConvertStringSidToSid for - backwards compatibility with Windows NT4/ME (ANSI build only). -* Loads RegSetKeySecurity/RegGetKeySecurity functions at run-time for - backwards compatibility with Windows NT4/ME (ANSI build only). -* Removed commented out legacy code. - -v1.0.5.0 - 25th January 2012 - Afrow UK -* Removed IsUserTheAdministrator. -* Added NameToSid. -* Major code cleanup/rewrite. -* Proper Unicode build (with Unicode plugin API). -* Support for 64-bit registry (SetRegView 64). -* Functions now return "ok" on success or "error" otherwise. On "error", - the next item on the stack will be the error description. -* Added version information resource. - -23rd January 2008 - Afrow UK -* Added function IsUserTheAdministrator. -* Cleaned up code. Rebuilt as pure cpp, decreasing DLL size. -* No longer using gobal temp variable for strings. - -7th January 2008 - Afrow UK -* Fixed registry instructions. - -8th November 2007 - Afrow UK -* EnableInheritance/DisableInheritance names changed. -* Functions added: - EnableFileInheritance - DisableFileInheritance - EnableRegKeyInheritance - DisableRegKeyInheritance - GetFileOwner - GetFileGroup - GetRegKeyOwner - GetRegKeyGroup - ClearOnFile - ClearOnRegKey - GetCurrentUserName - SidToName - -21st August 2007 - Afrow UK -* Added /noinherit switch to prevent child objects inheriting a - particular permission. -* Added EnableInheritance and DisableInheritance functions. -* Removed code to print items in the install log. - -13th July 2007 - kichik -* Return proper error codes (return value instead of GetLastError()) - -30th June 2006 - Afrow UK -* Error MessageBox removed. -* Error messages are now just returned on NSIS stack. - - -CONVENTIONS ------------ - - - A valid Windows(tm) filename (ie. "C:\WINDOWS\" or - "\\HOSTNAME\SHARE"). - - - The well-known root of a registry key. Following values are defined: - - HKCR - HKEY_CLASSES_ROOT - HKLM - HKEY_LOCAL_MACHINE - HKCU - HKEY_CURRENT_USER - HKU - HKEY_USERS - - - The name of the registry to alter (ie. "Software\Microsoft\Windows"). - - - A valid Windows(tm) account. The account can be specified as relative - account name (ie. "Administrator" or "Everyone"), a qualified account - name (ie. "Domain\Administrator") or as security identifier (SID, - ie. "(S-1-5-32-545)"). "BUILTIN\USERS" is also a valid account name. - For a list of trustee names, open up - Control Panel > Administrative Tools > Computer Management > - Local Users and Groups. - WinNT4 uses a emulated version of ConvertStringSidToSid and - only supports the following SDDL strings: AN, AU, BA, BU, IU, SY and WD - - - A combination of access rights (ie. "FullAccess" or - "GenericRead + GenericWrite"). - For a full list of access rights, open the AccessControl.cpp source - file in Notepad. - -/NOINHERIT - Ensures the specified ACEs (Access Control Entries) are not inherited - by child nodes (i.e for directory or registry key objects). - -HANDLING ERRORS ---------------- - -To handle errors, check the result on the stack: - - AccessControl::SetOnRegKey HKLM Software\MyApp Stuart FullAccess - Pop $R0 - ${If} $R0 == error - Pop $R0 - DetailPrint `AccessControl error: $R0` - ${EndIf} - -FUNCTIONS ---------- - -GrantOnFile [/NOINHERIT] -GrantOnRegKey [/NOINHERIT] -Pop $Result ; "ok" or "error" + error details - - Makes sure that the trustee get the requested access rights on - that object. - ---------- - -SetOnFile [/NOINHERIT] -SetOnRegKey [/NOINHERIT] -Pop $Result ; "ok" or "error" + error details - - Replaces any existing access rights for the trustee on the object - with the specified access rights. - ---------- - -ClearOnFile [/NOINHERIT] -ClearOnRegKey [/NOINHERIT] -Pop $Result ; "ok" or "error" + error details - - Replaces all trustees on the object with the specified trustee and - access rights. - ---------- - -DenyOnFile [/NOINHERIT] -DenyOnRegKey [/NOINHERIT] -Pop $Result ; "ok" or "error" + error details - - Explicitly denies an access right on a object. - ---------- - -RevokeOnFile [/NOINHERIT] -RevokeOnRegKey [/NOINHERIT] -Pop $Result ; "ok" or "error" + error details - - Removes a formerly defined access right for that object. - Note that access rights will still be revoked even if they are - inherited. - ---------- - -SetFileOwner -SetRegKeyOwner -Pop $Result ; "ok" or "error" + error details - - Changes the owner of an object. - ---------- - -GetFileOwner -GetRegKeyOwner -Pop $Owner ; or "error" + error details - - Gets the owner of an object. - ---------- - -SetFileGroup -SetRegKeyGroup -Pop $Result ; "ok" or "error" + error details - - Changes the primary group of the object. - ---------- - -GetFileGroup -GetRegKeyGroup -Pop $Group ; or "error" + error details - - Gets the primary group of the object. - ---------- - -EnableFileInheritance -EnableRegKeyInheritance -Pop $Result ; "ok" or "error" + error details - - Enables inheritance of parent object permissions. - ---------- - -DisableFileInheritance -DisableRegKeyInheritance -Pop $Result ; "ok" or "error" + error details - - Disables inheritance of parent object permissions. - ---------- - -SidToName -Pop $Domain ; or "error" + error details -Pop $Username - - Converts an SID on the local machine to the corresponding username and - domain name. - ---------- - -NameToSid -Pop $SID ; or "error" + error details - - Gets the SID of the specified username on the local machine. - ---------- - -GetCurrentUserName -Pop $Username ; or "error" - - Gets the username of the current user running the setup. - ---------- \ No newline at end of file diff --git a/windows-dependencies/AccessControl/Plugins/AccessControl.dll b/windows-dependencies/AccessControl/Plugins/AccessControl.dll deleted file mode 100644 index 0de0324fc..000000000 Binary files a/windows-dependencies/AccessControl/Plugins/AccessControl.dll and /dev/null differ diff --git a/windows-dependencies/AccessControl/Unicode/Plugins/AccessControl.dll b/windows-dependencies/AccessControl/Unicode/Plugins/AccessControl.dll deleted file mode 100644 index cc27f63f4..000000000 Binary files a/windows-dependencies/AccessControl/Unicode/Plugins/AccessControl.dll and /dev/null differ diff --git a/windows-dependencies/Java/COPYRIGHT b/windows-dependencies/Java/COPYRIGHT deleted file mode 100644 index fcb673091..000000000 --- a/windows-dependencies/Java/COPYRIGHT +++ /dev/null @@ -1,70 +0,0 @@ -Copyright 2006, 2011, Oracle and/or its affiliates. -All rights reserved. - -This software and related documentation are provided under a -license agreement containing restrictions on use and -disclosure and are protected by intellectual property laws. -Except as expressly permitted in your license agreement or -allowed by law, you may not use, copy, reproduce, translate, -broadcast, modify, license, transmit, distribute, exhibit, -perform, publish, or display any part, in any form, or by -any means. Reverse engineering, disassembly, or -decompilation of this software, unless required by law for -interoperability, is prohibited. - -The information contained herein is subject to change -without notice and is not warranted to be error-free. If -you find any errors, please report them to us in writing. - -If this is software or related software documentation that -is delivered to the U.S. Government or anyone licensing it -on behalf of the U.S. Government, the following notice is -applicable: - -U.S. GOVERNMENT RIGHTS Programs, software, databases, and -related documentation and technical data delivered to U.S. -Government customers are "commercial computer software" or -"commercial technical data" pursuant to the applicable -Federal Acquisition Regulation and agency-specific -supplemental regulations. As such, the use, duplication, -disclosure, modification, and adaptation shall be subject to -the restrictions and license terms set forth in the -applicable Government contract, and, to the extent -applicable by the terms of the Government contract, the -additional rights set forth in FAR 52.227-19, Commercial -Computer Software License (December 2007). Oracle America, -Inc., 500 Oracle Parkway, Redwood City, CA 94065. - -This software or hardware is developed for general use in a -variety of information management applications. It is not -developed or intended for use in any inherently dangerous -applications, including applications which may create a risk -of personal injury. If you use this software or hardware in -dangerous applications, then you shall be responsible to -take all appropriate fail-safe, backup, redundancy, and -other measures to ensure its safe use. Oracle Corporation -and its affiliates disclaim any liability for any damages -caused by use of this software or hardware in dangerous -applications. - -Oracle and Java are registered trademarks of Oracle and/or -its affiliates. Other names may be trademarks of their -respective owners. - -AMD, Opteron, the AMD logo, and the AMD Opteron logo are -trademarks or registered trademarks of Advanced Micro -Devices. Intel and Intel Xeon are trademarks or registered -trademarks of Intel Corporation. All SPARC trademarks are -used under license and are trademarks or registered -trademarks of SPARC International, Inc. UNIX is a -registered trademark licensed through X/Open Company, Ltd. - -This software or hardware and documentation may provide -access to or information on content, products, and services -from third parties. Oracle Corporation and its affiliates -are not responsible for and expressly disclaim all -warranties of any kind with respect to third-party content, -products, and services. Oracle Corporation and its -affiliates will not be responsible for any loss, costs, or -damages incurred due to your access to or use of third-party -content, products, or services. diff --git a/windows-dependencies/Java/Data/PortableApps.comInstaller/license.ini b/windows-dependencies/Java/Data/PortableApps.comInstaller/license.ini deleted file mode 100644 index d1d175859..000000000 --- a/windows-dependencies/Java/Data/PortableApps.comInstaller/license.ini +++ /dev/null @@ -1,2 +0,0 @@ -[PortableApps.comInstaller] -EULAVersion=1 diff --git a/windows-dependencies/Java/JavaPortable.ini b/windows-dependencies/Java/JavaPortable.ini deleted file mode 100644 index 3a425c77c..000000000 --- a/windows-dependencies/Java/JavaPortable.ini +++ /dev/null @@ -1,4 +0,0 @@ -[JavaPortable] -Vendor=Sun Microsystems Inc. -Version=1.6.0_30 -URL=http://java.sun.com/ \ No newline at end of file diff --git a/windows-dependencies/Java/LICENSE b/windows-dependencies/Java/LICENSE deleted file mode 100644 index 39e216aa1..000000000 --- a/windows-dependencies/Java/LICENSE +++ /dev/null @@ -1 +0,0 @@ -Please refer to http://java.com/license diff --git a/windows-dependencies/Java/LICENSE.txt b/windows-dependencies/Java/LICENSE.txt deleted file mode 100644 index 39e216aa1..000000000 --- a/windows-dependencies/Java/LICENSE.txt +++ /dev/null @@ -1 +0,0 @@ -Please refer to http://java.com/license diff --git a/windows-dependencies/Java/Other/Source/PluginEULA.rtf b/windows-dependencies/Java/Other/Source/PluginEULA.rtf deleted file mode 100644 index 70cb06dc8..000000000 --- a/windows-dependencies/Java/Other/Source/PluginEULA.rtf +++ /dev/null @@ -1,53 +0,0 @@ -{\rtf1\ansi\ansicpg1252\deff0\deflang1033{\fonttbl{\f0\fnil\fcharset0 Calibri;}} -{\colortbl ;\red0\green0\blue255;} -{\*\generator Msftedit 5.41.21.2509;}\viewkind4\uc1\pard\sa200\sl276\slmult1\qc\lang9\f0\fs22 Oracle Corporation Binary Code License Agreement\line for the JAVA SE RUNTIME ENVIRONMENT (JRE) VERSION 6 and JAVAFX RUNTIME\par -\pard\sa200\sl276\slmult1\par -ORACLE CORPORATION ("ORACLE") IS WILLING TO LICENSE THE SOFTWARE IDENTIFIED BELOW TO YOU ONLY UPON THE CONDITION THAT YOU ACCEPT ALL OF THE TERMS CONTAINED IN THIS BINARY CODE LICENSE AGREEMENT AND SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT"). PLEASE READ THE AGREEMENT CAREFULLY. BY USING THE SOFTWARE YOU ACKNOWLEDGE THAT YOU HAVE READ THE TERMS AND AGREE TO THEM. IF YOU ARE AGREEING TO THESE TERMS ON BEHALF OF A COMPANY OR OTHER LEGAL ENTITY, YOU REPRESENT THAT YOU HAVE THE LEGAL AUTHORITY TO BIND THE LEGAL ENTITY TO THESE TERMS. IF YOU DO NOT HAVE SUCH AUTHORITY, OR IF YOU DO NOT WISH TO BE BOUND BY THE TERMS, THEN YOU MUST NOT USE THE SOFTWARE ON THIS SITE OR ANY OTHER MEDIA ON WHICH THE SOFTWARE IS CONTAINED.\par -\par - 1. \b DEFINITIONS\b0 . "Software" means the identified above in binary form, any other machine readable materials (including, but not limited to, libraries, source files, header files, and data files), any updates or error corrections provided by Oracle, and any user manuals, programming guides and other documentation provided to you by Oracle under this Agreement. "General Purpose Desktop Computers and Servers" means computers, including desktop and laptop computers, or servers, used for general computing functions under end user control (such as but not specifically limited to email, general purpose Internet browsing, and office suite productivity tools). The use of Software in systems and solutions that provide dedicated functionality (other than as mentioned above) or designed for use in embedded or function-specific software applications, for example but not limited to: Software embedded in or bundled with industrial control systems, wireless mobile telephones, wireless handheld devices, netbooks, kiosks, TV/STB, Blu-ray Disc devices, telematics and network control switching equipment, printers and storage management systems, and other related systems are excluded from this definition and not licensed under this Agreement. "Programs" means (a) Java technology applets and applications intended to run on the Java Platform Standard Edition (Java SE) platform on Java-enabled General Purpose Desktop Computers and Servers, and (b) JavaFX technology applications intended to run on the JavaFX Runtime on JavaFX-enabled General Purpose Desktop Computers and Servers.\par -\par - 2. \b LICENSE TO USE\b0 . Subject to the terms and conditions of this Agreement, including, but not limited to the Java Technology Restrictions of the Supplemental License Terms, Oracle grants you a non-exclusive, non-transferable, limited license without license fees to reproduce and use internally Software complete and unmodified for the sole purpose of running Programs. Additional licenses for developers and/or publishers are granted in the Supplemental License Terms.\par -\par - 3. \b RESTRICTIONS\b0 . Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Oracle and/or its licensors. Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software. You acknowledge that Licensed Software is not designed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Oracle Corporation disclaims any express or implied warranty of fitness for such uses. No right, title or interest in or to any trademark, service mark, logo or trade name of Oracle or its licensors is granted under this Agreement. Additional restrictions for developers and/or publishers licenses are set forth in the Supplemental License Terms.\par -\par - 4. \b LIMITED WARRANTY\b0 . Oracle warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided "AS IS". Your exclusive remedy and Oracle's entire liability under this limited warranty will be at Oracle's option to replace Software media or refund the fee paid for Software. Any implied warranties on the Software are limited to 90 days. Some states do not allow limitations on duration of an implied warranty, so the above may not apply to you. This limited warranty gives you specific legal rights. You may have others, which vary from state to state.\par -\par - 5. \b DISCLAIMER OF WARRANTY\b0 . UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID.\par -\par - 6. \b LIMITATION OF LIABILITY\b0 . TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL ORACLE OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF ORACLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Oracle's liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose. Some states do not allow the exclusion of incidental or consequential damages, so some of the terms above may not be applicable to you.\par -\par - 7. \b TERMINATION\b0 . This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Oracle if you fail to comply with any provision of this Agreement. Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right. Upon Termination, you must destroy all copies of Software.\par -\par - 8.\b EXPORT REGULATIONS\b0 . All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you.\par -\par - 9. \b TRADEMARKS AND LOGOS\b0 . You acknowledge and agree as between you and Oracle that Oracle owns the ORACLE, SUN, SOLARIS, JAVA, JINI, FORTE, and iPLANET trademarks and all ORACLE, SOLARIS, JAVA, JINI, FORTE, and iPLANET-related trademarks, service marks, logos and other brand designations ("Oracle Marks"), and you agree to comply with the Third Party Usage Guidelines currently located at {\field{\*\fldinst{HYPERLINK "http://www.oracle.com/html/3party.html"}}{\fldrslt{\ul\cf1 http://www.oracle.com/html/3party.html}}}\f0\fs22 Any use you make of the Oracle Marks inures to Oracle's benefit.\par -\par - 10. \b U.S. GOVERNMENT RESTRICTED RIGHTS\b0 . If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Government's rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions).\par -\par - 11.\b GOVERNING LAW\b0 . Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply.\par -\par - 12. \b SEVERABILITY\b0 . If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate.\par -\par - 13. \b INTEGRATION\b0 . This Agreement is the entire agreement between you and Oracle relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party.\par -\par -\pard\sa200\sl276\slmult1\qc SUPPLEMENTAL LICENSE TERMS\par -\pard\sa200\sl276\slmult1\par -These Supplemental License Terms add to or modify the terms of the Binary Code License Agreement. Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Binary Code License Agreement . These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Binary Code License Agreement, or in any license contained within the Software.\par -\par -\pard\sa200\sl276\slmult1 A. \b Software Internal Use and Development License Grant\b0 . Subject to the terms and conditions of this Agreement and restrictions and exceptions set forth in the Software "README" file incorporated herein by reference, including, but not limited to the Java Technology Restrictions of these Supplemental Terms, Oracle grants you a non-exclusive, non-transferable, limited license without fees to reproduce internally and use internally the Software complete and unmodified for the purpose of designing, developing, and testing your Programs.\par -\pard\sa200\sl276\slmult1\par - B. \b License to Distribute Software\b0 . Subject to the terms and conditions of this Agreement and restrictions and exceptions set forth in the Software README file, including, but not limited to the Java Technology Restrictions of these Supplemental Terms, Oracle grants you a non-exclusive, non-transferable, limited license without fees to reproduce and distribute the Software (except for the JavaFX Runtime), provided that (i) you distribute the Software complete and unmodified and only bundled as part of, and for the sole purpose of running, your Programs, (ii) the Programs add significant and primary functionality to the Software, (iii) you do not distribute additional software intended to replace any component(s) of the Software, (iv) you do not remove or alter any proprietary legends or notices contained in the Software, (v) you only distribute the Software subject to a license agreement that protects Oracle's interests consistent with the terms contained in this Agreement, and (vi) you agree to defend and indemnify Oracle and its licensors from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys' fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software.\par -\par - C. \b Java Technology Restrictions\b0 . You may not create, modify, or change the behavior of, or authorize your licensees to create, modify, or change the behavior of, classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar convention as specified by Oracle in any naming convention designation.\par -\par - D. \b Source Code\b0 . Software may contain source code that, unless expressly licensed for other purposes, is provided solely for reference purposes pursuant to the terms of this Agreement. Source code may not be redistributed unless expressly provided for in this Agreement.\par -\par - E.\b Third Party Code\b0 . Additional copyright notices and license terms applicable to portions of the Software are set forth in the THIRDPARTYLICENSEREADME.txt file. In addition to any terms and conditions of any third party opensource/freeware license identified in the THIRDPARTYLICENSEREADME.txt file, the disclaimer of warranty and limitation of liability provisions in paragraphs 5 and 6 of the Binary Code License Agreement shall apply to all Software in this distribution.\par -\par - F.\b Termination for Infringement\b0 . Either party may terminate this Agreement immediately should any Software become, or in either party's opinion be likely to become, the subject of a claim of infringement of any intellectual property right.\par -\par - G.\b Installation and Auto-Update\b0 . The Software's installation and auto-update processes transmit a limited amount of data to Oracle (or its service provider) about those specific processes to help Oracle understand and optimize them. Oracle does not associate the data with personally identifiable information. You can find more information about the data Oracle collects at {\field{\*\fldinst{HYPERLINK "http://java.com/data/"}}{\fldrslt{\ul\cf1 http://java.com/data/}}}\f0\fs22 .\par -\par -For inquiries please contact: Oracle Corporation, 500 Oracle Parkway, Redwood Shores, California 94065, USA. \par -} - \ No newline at end of file diff --git a/windows-dependencies/Java/Other/Source/PortableApps.comInstallerPluginCustom.nsh b/windows-dependencies/Java/Other/Source/PortableApps.comInstallerPluginCustom.nsh deleted file mode 100644 index 4371cd0c6..000000000 --- a/windows-dependencies/Java/Other/Source/PortableApps.comInstallerPluginCustom.nsh +++ /dev/null @@ -1,18 +0,0 @@ -!macro CustomCodePostInstall - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\charsets.pack" "$INSTDIR\lib\charsets.jar"' "" "" - Pop $R0 - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\deploy.pack" "$INSTDIR\lib\deploy.jar"' "" "" - Pop $R0 - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\javaws.pack" "$INSTDIR\lib\javaws.jar"' "" "" - Pop $R0 - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\jsse.pack" "$INSTDIR\lib\jsse.jar"' "" "" - Pop $R0 - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\plugin.pack" "$INSTDIR\lib\plugin.jar"' "" "" - Pop $R0 - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\rt.pack" "$INSTDIR\lib\rt.jar"' "" "" - Pop $R0 - ExecDOS::exec '"$INSTDIR\bin\unpack200.exe" -r -q "$INSTDIR\lib\ext\localedata.pack" "$INSTDIR\lib\ext\localedata.jar"' "" "" - Pop $R0 - CopyFiles /silent "$INSTDIR\bin\npdeployJava1.dll" "$INSTDIR\bin\new_plugin" - CopyFiles /silent "$INSTDIR\bin\msvcr71.dll" "$INSTDIR\bin\new_plugin" -!macroend diff --git a/windows-dependencies/Java/Other/Source/plugininstaller.ini b/windows-dependencies/Java/Other/Source/plugininstaller.ini deleted file mode 100644 index 49855a6a6..000000000 --- a/windows-dependencies/Java/Other/Source/plugininstaller.ini +++ /dev/null @@ -1,39 +0,0 @@ -[Format] -Type=PortableApps.comFormat -Version=2.0 - -[Details] -Name=jPortable -PluginName=jPortable -PluginType=CommonFiles -AppID=Java -Publisher=Oracle Corporation and PortableApps.com -Homepage=PortableApps.com -Category=Development -Description=jPortable is the Java runtime made portable that runs millions of software applications. -Language=Multilingual -Trademarks=Java is a trademark of Oracle Corporation. - -[License] -Shareable=true -OpenSource=false -Freeware=true -CommercialUse=true - -[Version] -PackageVersion=6.0.30.0 -DisplayVersion=6 Update 30 - -[Control] -Icons=1 -Start=Ignored.exe - -[DownloadFiles] -DownloadURL=http://javadl.sun.com/webapps/download/AutoDL?BundleId=58124 -DownloadMD5=ca598904090630d43f17050ce47db23a -DownloadName=Java -DownloadFilename=jre-6u30-windows-i586-s.exe -AdditionalInstallSize=35000 -DoubleExtractFilename=core.zip -DoubleExtract1To= -DoubleExtract1Filter=** \ No newline at end of file diff --git a/windows-dependencies/Java/README.txt b/windows-dependencies/Java/README.txt deleted file mode 100644 index cdb30f2c8..000000000 --- a/windows-dependencies/Java/README.txt +++ /dev/null @@ -1 +0,0 @@ -Please refer to http://java.com/licensereadme diff --git a/windows-dependencies/Java/THIRDPARTYLICENSEREADME.txt b/windows-dependencies/Java/THIRDPARTYLICENSEREADME.txt deleted file mode 100644 index 47ef27a04..000000000 --- a/windows-dependencies/Java/THIRDPARTYLICENSEREADME.txt +++ /dev/null @@ -1,3482 +0,0 @@ -DO NOT TRANSLATE OR LOCALIZE. - -%% The following software may be included in this product: CS CodeViewer v1.0; -Use of any of this software is governed by the terms of the license below: -Copyright 1999 by CoolServlets.com. - -Any errors or suggested improvements to this class can be reported as instructed -on CoolServlets.com. We hope you enjoy this program... your comments will -encourage further development! This software is distributed under the terms of -the BSD License. Redistribution and use in source and binary forms, with or -without modification, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. Neither name of -CoolServlets.com nor the names of its contributors may be used to endorse or -promote products derived from this software without specific prior written -permission. - -THIS SOFTWARE IS PROVIDED BY COOLSERVLETS.COM AND CONTRIBUTORS ``AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING INANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." - -%% The following software may be included in this product: Crimson v1.1.1 ; Use -of any of this software is governed by the terms of the license below: - -/* -* The Apache Software License, Version 1.1 -* -* -* Copyright (c) 1999-2000 The Apache Software Foundation. All rights reserved. -* -* Redistribution and use in source and binary forms, with or without -* modification, are permitted provided that the following conditions -* are met: -* -* 1. Redistributions of source code must retain the above copyright -* notice, this list of conditions and the following disclaimer. -* -* 2. Redistributions in binary form must reproduce the above copyright -* notice, this list of conditions and the following disclaimer in -* the documentation and/or other materials provided with the -* distribution. -* -* 3. The end-user documentation included with the redistribution, -* if any, must include the following acknowledgment: -* "This product includes software developed by the -* Apache Software Foundation (http://www.apache.org/)." -* Alternately, this acknowledgment may appear in the software itself, -* if and wherever such third-party acknowledgments normally appear. -* -* 4. The names "Crimson" and "Apache Software Foundation" must -* not be used to endorse or promote products derived from this -* software without prior written permission. For written -* permission, please contact apache@apache.org. -* -* 5. Products derived from this software may not be called "Apache", -* nor may "Apache" appear in their name, without prior written -* permission of the Apache Software Foundation. -* -* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED -* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR -* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF -* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT -* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -* SUCH DAMAGE. -* ====================================================================* -* This software consists of voluntary contributions made by many -* individuals on behalf of the Apache Software Foundation and was -* originally based on software copyright (c) 1999, International -* Business Machines, Inc., http://www.ibm.com. For more -* information on the Apache Software Foundation, please see -* . -*/ - - -%% The following software may be included in this product: Xalan J2; Use of any of this -software is governed by the terms of the license below: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and -distribution as defined by Sections 1 through 9 of this document. "Licensor" -shall mean the copyright owner or entity authorized by the copyright owner that -is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities -that control, are controlled by, or are under common control with that entity. -For the purposes of this definition, "control" means (i) the power, direct or -indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the -outstanding shares, or (iii) beneficial ownership of such entity. "You" (or -"Your") shall mean an individual or Legal Entity exercising permissions granted -by this License. - -"Source" form shall mean the preferred form for making modifications, including -but not limited to software source code, documentation source, and configuration -files. - -"Object" form shall mean any form resulting from mechanical transformation or -translation of a Source form, including but not limited to compiled object code, -generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made -available under the License, as indicated by a copyright notice that is included -in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that -is based on (or derived from) the Work and for which the editorial revisions, -annotations, elaborations, or other modifications represent, as a whole, an -original work of authorship. For the purposes of this License, Derivative Works -shall not include works that remain separable from, or merely link (or bind by -name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version -of the Work and any modifications or additions to that Work or Derivative Works -thereof, that is intentionally submitted to Licensor for inclusion in the Work -by the copyright owner or by an individual or Legal Entity authorized to submit -on behalf of the copyright owner. For the purposes of this definition, -"submitted" means any form of electronic, verbal, or written communication sent -to the Licensor or its representatives, including but not limited to -communication on electronic mailing lists, source code control systems, and -issue tracking systems that are managed by, or on behalf of, the Licensor for -the purpose of discussing and improving the Work, but excluding communication -that is conspicuously marked or otherwise designated in writing by the copyright -owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf -of whom a Contribution has been received by Licensor and subsequently -incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this -License, each Contributor hereby grants to You a perpetual, worldwide, -non-exclusive, no-charge, royalty-free, irrevocable copyright license to -reproduce, prepare Derivative Works of, publicly display, publicly perform, -sublicense, and distribute the Work and such Derivative Works in Source or -Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this -License, each Contributor hereby grants to You a perpetual, worldwide, -non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this -section) patent license to make, have made, use, offer to sell, sell, import, -and otherwise transfer the Work, where such license applies only to those patent -claims licensable by such Contributor that are necessarily infringed by their -Contribution(s) alone or by combination of their Contribution(s) with the Work -to which such Contribution(s) was submitted. If You institute patent litigation -against any entity (including a cross-claim or counterclaim in a lawsuit) -alleging that the Work or a Contribution incorporated within the Work -constitutes direct or contributory patent infringement, then any patent licenses -granted to You under this License for that Work shall terminate as of the date -such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or -Derivative Works thereof in any medium, with or without modifications, and in -Source or Object form, provided that You meet the following conditions: - -(a) You must give any other recipients of the Work or Derivative Works a copy of -this License; and - -(b) You must cause any modified files to carry prominent notices stating that -You changed the files; and - -(c) You must retain, in the Source form of any Derivative Works that You -distribute, all copyright, patent, trademark, and attribution notices from the -Source form of the Work, excluding those notices that do not pertain to any part -of the Derivative Works; and - -(d) If the Work includes a "NOTICE" text file as part of its distribution, then -any Derivative Works that You distribute must include a readable copy of the -attribution notices contained within such NOTICE file, excluding those notices -that do not pertain to any part of the Derivative Works, in at least one of the -following places: within a NOTICE text file distributed as part of the -Derivative Works; within the Source form or documentation, if provided along -with the Derivative Works; or, within a display generated by the Derivative -Works, if and wherever such third-party notices normally appear. The contents -of the NOTICE file are for informational purposes only and do not modify the -License. You may add Your own attribution notices within Derivative Works that -You distribute, alongside or as an addendum to the NOTICE text from the Work, -provided that such additional attribution notices cannot be construed as -modifying the License. - -You may add Your own copyright statement to Your modifications and may provide -additional or different license terms and conditions for use, reproduction, or -distribution of Your modifications, or for any such Derivative Works as a whole, -provided Your use,reproduction, and distribution of the Work otherwise complies -with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any -Contribution intentionally submitted for inclusion in the Work by You to the -Licensor shall be under the terms and conditions of this License, without any -additional terms or conditions. Notwithstanding the above, nothing herein shall -supersede or modify the terms of any separate license agreement you may have -executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, -trademarks, service marks, or product names of the Licensor, except as required -for reasonable and customary use in describing the origin of the Work and -reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in -writing, Licensor provides the Work (and each Contributor provides its -Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, either express or implied, including, without limitation, any warranties -or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A -PARTICULAR PURPOSE. You are solely responsible for determining the -appropriateness of using or redistributing the Work and assume any risks -associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in -tort (including negligence), contract, or otherwise, unless required by -applicable law (such as deliberate and grossly negligent acts) or agreed to in -writing, shall any Contributor be liable to You for damages, including any -direct, indirect, special, incidental, or consequential damages of any character -arising as a result of this License or out of the use or inability to use the -Work (including but not limited to damages for loss of goodwill, work stoppage, -computer failure or malfunction, or any and all other commercial damages or -losses), even if such Contributor has been advised of the possibility of such -damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work -or Derivative Works thereof, You may choose to offer,and charge a fee for, -acceptance of support, warranty, indemnity, or other liability obligations -and/or rights consistent with this License. However, in accepting such -obligations, You may act only on Your own behalf and on Your sole -responsibility, not on behalf of any other Contributor, and only if You agree to -indemnify, defend, and hold each Contributor harmless for any liability incurred -by, or claims asserted against, such Contributor by reason of your accepting any -such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - -To apply the Apache License to your work, attach the following boilerplate -notice, with the fields enclosed by brackets "[]" replaced with your own -identifying information. (Don't include the brackets!) The text should be -enclosed in the appropriate comment syntax for the file format. We also -recommend that a file or class name and description of purpose be included on -the same "printed page" as the copyright notice for easier identification within -third-party archives. - - Copyright [yyyy] [name of copyright owner] - -Licensed 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. - -%% The following software may be included in this product: NSIS 1.0j; Use of -any of this software is governed by the terms of the license below: -Copyright (C) 1999-2000 Nullsoft, Inc. - -This software is provided 'as-is', without any express or implied warranty. In -no event will the authors be held liable for any damages arising from the use of -this software. Permission is granted to anyone to use this software for any -purpose, including commercial applications, and to alter it and redistribute it -freely, subject to the following restrictions: - -1. The origin of this software must not be misrepresented; you must not claim -that you wrote the original software. If you use this software in a product, an -acknowledgment in the product documentation would be appreciated but is not -required. - -2. Altered source versions must be plainly marked as such, and must not be -misrepresented as being the original software. - -3. This notice may not be removed or altered from any source distribution. -Justin Frankel justin@nullsoft.com" - -%% Some Portions licensed from IBM are available at: -http://www.ibm.com/software/globalization/icu/ - -%% Portions Copyright Eastman Kodak Company 1992 - -%% Lucida is a registered trademark or trademark of Bigelow & Holmes in the U.S. -and other countries. - -%% Portions licensed from Taligent, Inc. - -%% The following software may be included in this product:IAIK PKCS Wrapper; Use -of any of this software is governed by the terms of the license below: - -Copyright (c) 2002 Graz University of Technology. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification,are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. The end-user documentation included with the redistribution, if any, must -include the following acknowledgment: - - "This product includes software developed by IAIK of Graz University of Technology." - -Alternately, this acknowledgment may appear in the software itself, if and -wherever such third-party acknowledgments normally appear. - -4. The names "Graz University of Technology" and "IAIK of Graz University of -Technology" must not be used to endorse or promote products derived from this -software without prior written permission. - -5. Products derived from this software may not be called "IAIK PKCS Wrapper", -nor may "IAIK" appear in their name, without prior written permission of Graz -University of Technology. - -THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LICENSOR -BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE -GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF -THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -%% The following software may be included in this product: Document Object -Model (DOM) v. Level 3; Use of any of this software is governed by the terms of -the license below: - -W3C SOFTWARE NOTICE AND LICENSE - -http://www.w3.org/Consortium/Legal/2002/copyright-software-20021231 - -This work (and included software, documentation such as READMEs, or other -related items) is being provided by the copyright holders under the following -license. By obtaining, using and/or copying this work, you (the licensee) agree -that you have read, understood, and will comply with the following terms and -conditions. - -Permission to copy, modify, and distribute this software and its documentation, -with or without modification, for any purpose and without fee or royalty is -hereby granted, provided that you include the following on ALL copies of the -software and documentation or portions thereof, including modifications: - -1.The full text of this NOTICE in a location viewable to users of the -redistributed or derivative work. - -2.Any pre-existing intellectual property disclaimers, notices, or terms and - conditions. If none exist, the W3C Software Short Notice should be included - (hypertext is preferred, text is permitted) within the body of any - redistributed or derivative code. - -3.Notice of any changes or modifications to the files, including the date - changes were made. (We recommend you provide URIs to the location from which - the code is derived.) - -THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS -MAKENO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE -OR THAT THEUSE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD -PARTY PATENTS,COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. - -COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL -ORCONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. -The name and trademarks of copyright holders may NOT be used in advertising or -publicity pertaining to the software without specific, written prior permission. -Title to copyright in this software and any associated documentation will at all -times remain with copyright holders. - -____________________________________ - -This formulation of W3C's notice and license became active on December 31 2002. -This version removes the copyright ownership notice such that this license can -be used with materials other than those owned by the W3C, reflects that ERCIM is -now a host of the W3C, includes references to this specific dated version of the -license, and removes the ambiguous grant of "use". Otherwise, this version is -the same as the previous version and is written so as to preserve the Free -Software Foundation's assessment of GPL compatibility and OSI's certification -under the Open Source Definition. Please see our Copyright FAQ for common -questions about using materials from our site, including specific terms and -conditions for packages like libwww, Amaya, and Jigsaw. Other questions about -this notice can be directed to site-policy@w3.org. - -%% The following software may be included in this product: Xalan, Xerces; Use -of any of this software is governed by the terms of the license below: /* - - * The Apache Software License, Version 1.1 - * - * - * Copyright (c) 1999-2003 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, - * if any, must include the following acknowledgment: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowledgment may appear in the software itself, - * if and wherever such third-party acknowledgments normally appear. - * - * 4. The names "Xerces" and "Apache Software Foundation" must - * not be used to endorse or promote products derived from this - * software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache", - * nor may "Apache" appear in their name, without prior written - * permission of the Apache Software Foundation. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * This software consists of voluntary contributions made by many - * individuals on behalf of the Apache Software Foundation and was - * originally based on software copyright (c) 1999, International - * Business Machines, Inc., http://www.ibm.com. For more - * information on the Apache Software Foundation, please see http://www.apache.org - * - -%% The following software may be included in this product: W3C XML Conformance -Test Suites v. 20020606; Use of any of this software is governed by the terms -of the license below: - -W3C SOFTWARE NOTICE AND LICENSE - -Copyright 1994-2002 World Wide Web Consortium, (Massachusetts Institute of -Technology, Institut National de Recherche en Informatique et en -Automatique,Keio University). All Rights Reserved. -http://www.w3.org/Consortium/Legal/ - -This W3C work (including software, documents, or other related items) is being -provided by the copyright holders under the following license. By -obtaining,using and/or copying this work, you (the licensee) agree that you have -read,understood, and will comply with the following terms and conditions: - -Permission to use, copy, modify, and distribute this software and its -documentation, with or without modification, for any purpose and without fee -orroyalty is hereby granted, provided that you include the following on ALL -copiesof the software and documentation or portions thereof, including -modifications,that you make: - -1. The full text of this NOTICE in a location viewable to users of the -redistributed or derivative work. - -2. Any pre-existing intellectual property disclaimers, notices, or terms and -conditions. If none exist, a short notice of the following form (hypertext is -preferred, text is permitted) should be used within the body of any -redistributed or derivative code: "Copyright [$date-of-software] World Wide Web -Consortium, (Massachusetts Institute of Technology, Institut National -deRecherche en Informatique et en Automatique, Keio University). All Rights -Reserved. http://www.w3.org/Consortium/Legal/" - -3. Notice of any changes or modifications to the W3C files, including the date -changes were made. (We recommend you provide URIs to the location from which -the code is derived.) - -THIS SOFTWARE AND DOCUMENTATION IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS -MAKENO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT -LIMITEDTO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY PARTICULAR PURPOSE -OR THATTHE USE OF THE SOFTWARE OR DOCUMENTATION WILL NOT INFRINGE ANY THIRD -PARTYPATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. - -COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL -ORCONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENTATION. - -The name and trademarks of copyright holders may NOT be used in advertising or -publicity pertaining to the software without specific, written prior permission. -Title to copyright in this software and any associated documentation will at all -times remain with copyright holders. - -____________________________________ - -This formulation of W3C's notice and license became active on August 14 1998 -soas to improve compatibility with GPL. This version ensures that W3C software -licensing terms are no more restrictive than GPL and consequently W3C software -may be distributed in GPL packages. See the older formulation for the policy -prior to this date. Please see our Copyright FAQ for common questions about -using materials from our site, including specific terms and conditions for -packages like libwww, Amaya, and Jigsaw. Other questions about this notice can -be directed to site-policy@w3.org. - -%% The following software may be included in this product: W3C XML Schema Test -Collection v. 1.16.2; Use of any of this software is governed by the terms of -the license below: W3C DOCUMENT NOTICE AND LICENSE - -Copyright 1994-2002 World Wide Web Consortium, (Massachusetts Institute of -Technology, Institut National de Recherche en Informatique et en -Automatique,Keio University). All Rights Reserved. -http://www.w3.org/Consortium/Legal/ - -Public documents on the W3C site are provided by the copyright holders under the -following license. The software or Document Type Definitions (DTDs) associated -with W3C specifications are governed by the Software Notice. By using and/or -copying this document, or the W3C document from which this statement is -linked,you (the licensee) agree that you have read, understood, and will comply -with the following terms and conditions: - -Permission to use, copy, and distribute the contents of this document, or theW3C -document from which this statement is linked, in any medium for any purpose and -without fee or royalty is hereby granted, provided that you include the -following on ALL copies of the document, or portions thereof, that you use: - -1. A link or URL to the original W3C document. - -2. The pre-existing copyright notice of the original author, or if it doesn't -exist, a notice of the form: "Copyright [$date-of-document] World Wide -WebConsortium, (Massachusetts Institute of Technology, Institut National -deRecherche en Informatique et en Automatique, Keio University). All Rights -Reserved. http://www.w3.org/Consortium/Legal/" (Hypertext is preferred, but -atextual representation is permitted.) - -3. If it exists, the STATUS of the W3C document. - -When space permits, inclusion of the full text of this NOTICE should be -provided. We request that authorship attribution be provided in any -software,documents, or other items or products that you create pursuant to the -implementation of the contents of this document, or any portion thereof. - -No right to create modifications or derivatives of W3C documents is granted -pursuant to this license. However, if additional requirements (documented in -the Copyright FAQ) are satisfied, the right to create modifications or -derivatives is sometimes granted by the W3C to individuals complying with those -requirements. THIS DOCUMENT IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO -REPRESENTATIONSOR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -WARRANTIES OFMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, -NON-INFRINGEMENT, OR TITLE;THAT THE CONTENTS OF THE DOCUMENT ARE SUITABLE FOR -ANY PURPOSE; NOR THAT THEIMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY -THIRD PARTY PATENTS,COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. - -COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL -ORCONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE DOCUMENT OR THE -PERFORMANCEOR IMPLEMENTATION OF THE CONTENTS THEREOF. - -The name and trademarks of copyright holders may NOT be used in advertising or -publicity pertaining to this document or its contents without specific, written -prior permission. Title to copyright in this document will at all times remain -with copyright holders. - ----------------------------------------------------------------------------- - -This formulation of W3C's notice and license became active on April 05 1999 soas -to account for the treatment of DTDs, schema's and bindings. See the older -formulation for the policy prior to this date. Please see our Copyright FAQ for -common questions about using materials from our site, including specific terms -and conditions for packages like libwww, Amaya, and Jigsaw. Other questions -about this notice can be directed to site-policy@w3.org. webmaster (last -updated by reagle on 1999/04/99.) - - -%% The following software may be included in this product: Mesa 3-D graphics -library v. 5; Use of any of this software is governed by the terms of the -license below: - -core Mesa code include/GL/gl.h Brian Paul -Mesa GLX driver include/GL/glx.h Brian Paul -Mesa Ext registry include/GL/glext.h SGI -SGI Free B include/GL/glxext.h - -Mesa license: - -The Mesa distribution consists of several components. Different copyrights and -licenses apply to different components. For example, GLUT is copyrighted by -Mark Kilgard, some demo programs are copyrighted by SGI, some of the Mesa device -drivers are copyrighted by their authors. See below for a list of Mesa's -components and the copyright/license for each. - -The core Mesa library is licensed according to the terms of the XFree86copyright -(an MIT-style license). This allows integration with the XFree86/DRIproject. -Unless otherwise stated, the Mesa source code and documentation is licensed as -follows: - -Copyright (C) 1999-2003 Brian Paul All Rights Reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"),to deal in the -Software without restriction, including without limitation the rights to use, -copy, modify, merge, publish, distribute, sublicense,and/or sell copies of the -Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESSOR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALLBRIAN PAUL BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER INAN ACTION OF -CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -SGI FREE SOFTWARE LICENSE B (Version 1.1 [02/22/2000]) -1. Definitions. - -1.1 "Additional Notice Provisions" means such additional provisions as appear in -the Notice in Original Code under the heading "Additional Notice Provisions." - -1.2 "Covered Code" means the Original Code or Modifications, or any combination -thereof. - -1.3 "Hardware" means any physical device that accepts input, processes input, -stores the results of processing, and/or provides output. - -1.4 "Larger Work" means a work that combines Covered Code or portions thereof -with code not governed by the terms of this License. - -1.5 "Licensable" means having the right to grant, to the maximum extent -possible, whether at the time of the initial grant or subsequently acquired, any -and all of the rights conveyed herein. - -1.6 "License" means this document. - -1.7 "Licensed Patents" means patent claims Licensable by SGI that are infringed -by the use or sale of Original Code or any Modifications provided by SGI, or any -combination thereof. - -1.8 "Modifications" means any addition to or deletion from the substance or -structure of the Original Code or any previous Modifications. When Covered Code -is released as a series of files, a Modification is: A. Any addition to the -contents of a file containing Original Code and/or addition to or deletion from -the contents of a file containing previous Modifications.B. Any new file that -contains any part of the Original Code or previous Modifications. - -1.9 "Notice" means any notice in Original Code or Covered Code, as required by -and in compliance with this License. - -1.10 "Original Code" means source code of computer software code that is -described in the source code Notice required by Exhibit A as Original Code, and -updates and error corrections specifically thereto. - -1.11 "Recipient" means an individual or a legal entity exercising rights under, -and complying with all of the terms of, this License or a future version of this -License issued under Section 8. For legal entities, "Recipient" includes any -entity that controls, is controlled by, or is under common control with -Recipient. For purposes of this definition, "control" of an entity means (a) -the power, direct or indirect, to direct or manage such entity, or (b) ownership -of fifty percent (50%) or more of the outstanding shares or beneficial ownership -of such entity. - -1.12 "Recipient Patents" means patent claims Licensable by a Recipient that are -infringed by the use or sale of Original Code or any Modifications provided by -SGI, or any combination thereof. - -1.13 "SGI" means Silicon Graphics, Inc. - -1.14 "SGI Patents" means patent claims Licensable by SGI other than the Licensed -Patents. - -2. License Grant and Restrictions. - -2.1 SGI License Grant. Subject to the terms of this License and any third party -intellectual property claims, for the duration of intellectual property -protections inherent in the Original Code, SGI hereby grants Recipient a -worldwide, royalty-free, non-exclusive license, to do the following: (i) under -copyrights Licensable by SGI, to reproduce, distribute, create derivative works -from, and, to the extent applicable, display and perform the Original Code -and/or any Modifications provided by SGI alone and/or as part of a Larger Work; -and (ii) under any Licensable Patents, to make, have made, use, sell, offer for -sale, import and/or otherwise transfer the Original Code and/or any -Modifications provided by SGI. Recipient accepts the terms and conditions of -this License by undertaking any of the aforementioned actions. The patent -license shall apply to the Covered Code if, at the time any related Modification -is added, such addition of the Modification causes such combination to be -covered by the Licensed Patents . The patent license in Section 2.1(ii) shall -not apply to any other combinations that include the Modification. No patent -license is provided under SGI Patents for infringements of SGI Patents by -Modifications not provided by SGI or combinations of Original Code and -Modifications not provided by SGI. - -2.2 Recipient License Grant. Subject to the terms of this License and any third -party intellectual property claims, Recipient hereby grants SGI and any other -Recipients a worldwide, royalty-free, non-exclusive license, under any Recipient -Patents, to make, have made, use, sell, offer for sale, import and/or otherwise -transfer the Original Code and/or any Modifications provided by SGI. - -2.3 No License For Hardware Implementations. The licenses granted in Section -2.1 and 2.2 are not applicable to implementation in Hardware of the algorithms -embodied in the Original Code or any Modifications provided by SGI . - -3. Redistributions. - -3.1 Retention of Notice/Copy of License. The Notice set forth in Exhibit A, -below, must be conspicuously retained or included in any and all redistributions -of Covered Code. For distributions of the Covered Code in source code form, the -Notice must appear in every file that can include a text comments field; in -executable form, the Notice and a copy of this License must appear in related -documentation or collateral where the Recipient's rights relating to Covered -Code are described. Any Additional Notice Provisions which actually appears in -the Original Code must also be retained or included in any and all -redistributions of Covered Code. - -3.2 Alternative License. Provided that Recipient is in compliance with the -terms of this License, Recipient may, so long as without derogation of any of -SGI's rights in and to the Original Code, distribute the source code and/or -executable version(s) of Covered Code under (1) this License; (2) a license -identical to this License but for only such changes as are necessary in order to -clarify Recipient's role as licensor of Modifications; and/or (3) a license of -Recipient's choosing, containing terms different from this License, provided -that the license terms include this Section 3 and Sections 4, 6, 7, 10, 12, and -13, which terms may not be modified or superseded by any other terms of such -license. If Recipient elects to use any license other than this License, -Recipient must make it absolutely clear that any of its terms which differ from -this License are offered by Recipient alone, and not by SGI. It is emphasized -that this License is a limited license, and, regardless of the license form -employed by Recipi ent in accordance with this Section 3.2, Recipient may -relicense only such rights, in Original Code and Modifications by SGI, as it has -actually been granted by SGI in this License. - -3.3 Indemnity. Recipient hereby agrees to indemnify SGI for any liability -incurred by SGI as a result of any such alternative license terms Recipient -offers. - -4. Termination. This License and the rights granted hereunder will terminate -automatically if Recipient breaches any term herein and fails to cure such -breach within 30 days thereof. Any sublicense to the Covered Code that is -properly granted shall survive any termination of this License, absent -termination by the terms of such sublicense. Provisions that, by their nature, -must remain in effect beyond the termination of this License, shall survive. - -5. No Trademark Or Other Rights. This License does not grant any rights to: -(i) any software apart from the Covered Code, nor shall any other rights or -licenses not expressly granted hereunder arise by implication, estoppel or -otherwise with respect to the Covered Code; (ii) any trade name, trademark or -service mark whatsoever, including without limitation any related right for -purposes of endorsement or promotion of products derived from the Covered Code, -without prior written permission of SGI; or (iii) any title to or ownership of -the Original Code, which shall at all times remains with SGI. All rights in the -Original Code not expressly granted under this License are reserved. - -6. Compliance with Laws; Non-Infringement. There are various worldwide laws, -regulations, and executive orders applicable to dispositions of Covered Code, -including without limitation export, re-export, and import control laws, -regulations, and executive orders, of the U.S. government and other countries, -and Recipient is reminded it is obliged to obey such laws, regulations, and -executive orders. Recipient may not distribute Covered Code that (i) in any way -infringes (directly or contributorily) any intellectual property rights of any -kind of any other person or entity or (ii) breaches any representation or -warranty, express, implied or statutory, to which, under any applicable law, it -might be deemed to have been subject. - -7. Claims of Infringement. If Recipient learns of any third party claim that -any disposition of Covered Code and/or functionality wholly or partially -infringes the third party's intellectual property rights, Recipient will -promptly notify SGI of such claim. - -8. Versions of the License. SGI may publish revised and/or new versions of the -License from time to time, each with a distinguishing version number. Once -Covered Code has been published under a particular version of the License, -Recipient may, for the duration of the license, continue to use it under the -terms of that version, or choose to use such Covered Code under the terms of any -subsequent version published by SGI. Subject to the provisions of Sections 3 -and 4 of this License, only SGI may modify the terms applicable to Covered Code -created under this License. - -9. DISCLAIMER OF WARRANTY. COVERED CODE IS PROVIDED "AS IS." ALL EXPRESS AND -IMPLIED WARRANTIES AND CONDITIONS ARE DISCLAIMED, INCLUDING, WITHOUT LIMITATION, -ANY IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, SATISFACTORY QUALITY, -FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. SGI ASSUMES NO RISK AS -TO THE QUALITY AND PERFORMANCE OF THE SOFTWARE. SHOULD THE SOFTWARE PROVE -DEFECTIVE IN ANY RESPECT, SGI ASSUMES NO COST OR LIABILITY FOR SERVICING, REPAIR -OR CORRECTION. THIS DISCLAIMER OF WARRANTY IS AN ESSENTIAL PART OF THIS -LICENSE. NO USE OF ANY COVERED CODE IS AUTHORIZED HEREUNDER EXCEPT SUBJECT TO -THIS DISCLAIMER. - -10. LIMITATION OF LIABILITY. UNDER NO CIRCUMSTANCES NOR LEGAL THEORY, WHETHER -TORT (INCLUDING, WITHOUT LIMITATION, NEGLIGENCE OR STRICT LIABILITY), CONTRACT, -OR OTHERWISE, SHALL SGI OR ANY SGI LICENSOR BE LIABLE FOR ANY DIRECT, INDIRECT, -SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER INCLUDING, -WITHOUT LIMITATION, DAMAGES FOR LOSS OF GOODWILL, WORK STOPPAGE, LOSS OF DATA, -COMPUTER FAILURE OR MALFUNCTION, OR ANY AND ALL OTHER COMMERCIAL DAMAGES OR -LOSSES, EVEN IF SUCH PARTY SHALL HAVE BEEN INFORMED OF THE POSSIBILITY OF SUCH -DAMAGES. THIS LIMITATION OF LIABILITY SHALL NOT APPLY TO LIABILITY FOR DEATH OR -PERSONAL INJURY RESULTING FROM SGI's NEGLIGENCE TO THE EXTENT APPLICABLE LAW -PROHIBITS SUCH LIMITATION. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OR -LIMITATION OF INCIDENTAL OR CONSEQUENTIAL DAMAGES, SO THAT EXCLUSION AND -LIMITATION MAY NOT APPLY TO RECIPIENT. - -11. Indemnity. Recipient shall be solely responsible for damages arising, -directly or indirectly, out of its utilization of rights under this License. -Recipient will defend, indemnify and hold harmless Silicon Graphics, Inc. from -and against any loss, liability, damages, costs or expenses (including the -payment of reasonable attorneys fees) arising out of Recipient's use, -modification, reproduction and distribution of the Covered Code or out of any -representation or warranty made by Recipient. - -12. U.S. Government End Users. The Covered Code is a "commercial item" -consisting of "commercial computer software" as such terms are defined in title -48 of the Code of Federal Regulations and all U.S. Government End Users acquire -only the rights set forth in this License and are subject to the terms of this -License. - -13. Miscellaneous. This License represents the complete agreement concerning -the its subject matter. If any provision of this License is held to be -unenforceable, such provision shall be reformed so as to achieve as nearly as -possible the same legal and economic effect as the original provision and the -remainder of this License will remain in effect. This License shall be governed -by and construed in accordance with the laws of the United States and the State -of California as applied to agreements entered into and to be performed entirely -within California between California residents. Any litigation relating to this -License shall be subject to the exclusive jurisdiction of the Federal Courts of -the Northern District of California (or, absent subject matter jurisdiction in -such courts, the courts of the State of California), with venue lying -exclusively in Santa Clara County, California, with the losing party responsible -for costs, including without limitation, court costs and reasonable attorneys -fees and ex penses. The application of the United Nations Convention on -Contracts for the International Sale of Goods is expressly excluded. Any law or -regulation that provides that the language of a contract shall be construed -against the drafter shall not apply to this License. - -Exhibit A License Applicability. Except to the extent portions of this file are -made subject to an alternative license as permitted in the SGI Free Software -License B, Version 1.1 (the "License"), the contents of this file are subject -only to the provisions of the License. You may not use this file except in -compliance with the License. You may obtain a copy of the License at Silicon -Graphics, Inc., attn: Legal Services, 1600 Amphitheatre Parkway, Mountain View, -CA 94043-1351, or at: http://oss.sgi.com/projects/FreeB Note that, as provided -in the License, the Software is distributed on an "AS IS" basis, with ALL -EXPRESS AND IMPLIED WARRANTIES AND CONDITIONS DISCLAIMED, INCLUDING, WITHOUT -LIMITATION, ANY IMPLIED WARRANTIES AND CONDITIONS OF MERCHANTABILITY, -SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. -Original Code. The Original Code is: [name of software, version number, and -release date], developed by Silicon Graphics, Inc. The Original Code is -Copyright (c) [dates of first publication, as appearing in the Notice in the -Original Code] Silicon Graphics, Inc. Copyright in any portions created by -third parties is as indicated elsewhere herein. All Rights Reserved. -Additional Notice Provisions: [such additional provisions, if any, as appear in -the Notice in the Original Code under the heading "Additional Notice -Provisions"] - -%% The following software may be included in this product: Byte Code -Engineering Library (BCEL) v. 5; Use of any of this software is governed by the -terms of the license below: - -Apache Software License - -/ -==================================================================== -The Apache Software License, Version 1.1 - -Copyright (c) 2001 The Apache Software Foundation. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials providedwith the distribution. - -3. The end-user documentation included with the redistribution, if any, must -include the following acknowledgment: "This product includes software developed -by the Apache Software Foundation (http://www.apache.org/)." Alternately, this -acknowledgment may appear in the software itself, if and wherever such -third-party acknowledgments normally appear. - -4. The names "Apache" and "Apache Software Foundation"and "Apache BCEL" must -not be used to endorse or promote products derived from this software without -prior written permission. For written permission, please contact -apache@apache.org. - -5. Products derived from this software may not be called"Apache", "Apache -BCEL", nor may "Apache" appear in their name,without prior written permission of -the Apache Software Foundation. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED ORIMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIEDWARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSEARE DISCLAIMED. IN NO EVENT SHALL THE APACHE -SOFTWAREFOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT,INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,BUT -NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;LOSS OF USE, DATA, -OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVERCAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICTLIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING INANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THEPOSSIBILITY OF SUCH DAMAGE. -==================================================================== - -This software consists of voluntary contributions made by many individuals on -behalf of the Apache Software Foundation. For more information on the Apache -Software Foundation, please see http://www.apache.org. / - - - -%% The following software may be included in this product: Regexp, Regular -Expression Package v. 1.2; Use of any of this software is governed by the terms -of the license below: The Apache Software License, Version 1.1 - -Copyright (c) 2001 The Apache Software Foundation. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification,are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. The end-user documentation included with the redistribution, if any, must -include the following acknowledgment: "This product includes software developed -by the Apache Software Foundation (http://www.apache.org/)." Alternately, this -acknowledgment may appear in the software itself, if and wherever such -third-party acknowledgments normally appear. - -4. The names "Apache" and "Apache Software Foundation" and "Apache Turbine" -must not be used to endorse or promote products derived from this software -without prior written permission. For written permission, please contact -apache@apache.org. - -5. Products derived from this software may not be called "Apache", "Apache -Turbine", nor may "Apache" appear in their name, without prior written -permission of the Apache Software Foundation. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE -SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -==================================================================== - -This software consists of voluntary contributions made by many individuals on -behalf of the Apache Software Foundation. For more information on the Apache -Software Foundation, please see http://www.apache.org. - -%% The following software may be included in this product: CUP Parser Generator -for Java v. 0.10k; Use of any of this software is governed by the terms of the -license below: CUP Parser Generator Copyright Notice, License, and Disclaimer - -Copyright 1996-1999 by Scott Hudson, Frank Flannery, C. Scott Ananian - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, provided that -the above copyright notice appear in all copies and that both the copyright -notice and this permission notice and warranty disclaimer appear in supporting -documentation, and that the names of the authors or their employers not be used -in advertising or publicity pertaining to distribution of the software without -specific, written prior permission. - -The authors and their employers disclaim all warranties with regard to this -software, including all implied warranties of merchantability and fitness. In -no event shall the authors or their employers be liable for any special, -indirect or consequential damages or any damages whatsoever resulting from loss -of use, data or profits, whether in an action of contract,negligence or other -tortious action, arising out of or in connection with the use or performance of -this software. - -%% The following software may be included in this product: JLex: A Lexical -Analyzer Generator for Java v. 1.2.5; Use of any of this software is governed -by the terms of the license below: JLEX COPYRIGHT NOTICE, LICENSE AND -DISCLAIMER. - -Copyright 1996-2003 by Elliot Joel Berk and C. Scott Ananian - -Permission to use, copy, modify, and distribute this software and its -documentation for any purpose and without fee is hereby granted, provided that -the above copyright notice appear in all copies and that both the copyright -notice and this permission notice and warranty disclaimer appear in supporting -documentation, and that the name of the authors or their employers not be used -in advertising or publicity pertaining to distribution of the software without -specific, written prior permission. - -The authors and their employers disclaim all warranties with regard to this -software, including all implied warranties of merchantability and fitness. In -no event shall the authors or their employers be liable for any special, -indirect or consequential damages or any damages whatsoever resulting from loss -of use, data or profits, whether in an action of contract, negligence or other -tortious action, arising out of or in connection with the use or performance of -this software. - -Java is a trademark of Oracle Corporation. References to the Java -programming language in relation to JLex are not meant to imply that Oracle -endorses this product. - -%% The following software may be included in this product: SAX v. 2.0.1; Use -of any of this software is governed by the terms of the license below: -Copyright Status - -SAX is free! - -In fact, it's not possible to own a license to SAX, since it's been placed in -the public domain. - -No Warranty - -Because SAX is released to the public domain, there is no warranty for the -design or for the software implementation, to the extent permitted by applicable -law. Except when otherwise stated in writing the copyright holders and/or other -parties provide SAX "as is" without warranty of any kind, either expressed or -implied, including, but not limited to, the implied warranties of -merchantability and fitness for a particular purpose. The entire risk as to the -quality and performance of SAX is with you. Should SAX prove defective, you -assume the cost of all necessary servicing, repair or correction. - -In no event unless required by applicable law or agreed to in writing will any -copyright holder, or any other party who may modify and/or redistribute SAX, be -liable to you for damages, including any general, special, incidental or -consequential damages arising out of the use or inability to use SAX (including -but not limited to loss of data or data being rendered inaccurate or losses -sustained by you or third parties or a failure of the SAX to operate with any -other programs), even if such holder or other party has been advised of the -possibility of such damages. - -Copyright Disclaimers - -This page includes statements to that effect by David Megginson, who would have -been able to claim copyright for the original work. - -SAX 1.0 - -Version 1.0 of the Simple API for XML (SAX), created collectively by the -membership of the XML-DEV mailing list, is hereby released into the public -domain. - -No one owns SAX: you may use it freely in both commercial and non-commercial -applications, bundle it with your software distribution, include it on a CD-ROM, -list the source code in a book, mirror the documentation at your own web site, -or use it in any other way you see fit. - -David Megginson, sax@megginson.com -1998-05-11 - -SAX 2.0 - -I hereby abandon any property rights to SAX 2.0 (the Simple API for XML), and -release all of the SAX 2.0 source code, compiled code, and documentation -contained in this distribution into the Public Domain. SAX comes with NO -WARRANTY or guarantee of fitness for any purpose. - -David Megginson, david@megginson.com -2000-05-05 - -%% The following software may be included in this product: Cryptix; Use of any -of this software is governed by the terms of the license below: - -Cryptix General License - -Copyright © 1995-2003 The Cryptix Foundation Limited. All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1.Redistributions of source code must retain the copyright notice, this list of -conditions and the following disclaimer. - -2.Redistributions in binary form must reproduce the above copyright notice, this -list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY -THE CRYPTIX FOUNDATION LIMITED AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS -ORIMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FORA PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -SHALL THE CRYPTIX FOUNDATION LIMITED OR CONTRIBUTORS BELIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOTLIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESSINTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT(INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OFTHE POSSIBILITY OF SUCH DAMAGE. - -%% The following software may be included in this product: W3C XML Schema Test -Collection; Use of any of this software is governed by the terms of the license -below: - -W3C DOCUMENT NOTICE AND LICENSE - -Copyright 1994-2002 World Wide Web Consortium, (Massachusetts Institute of -Technology, Institut National de Recherche en Informatique et en -Automatique,Keio University). All Rights Reserved. - -http://www.w3.org/Consortium/Legal/ - -Public documents on the W3C site are provided by the copyright holders under the -following license. The software or Document Type Definitions (DTDs) associated -with W3C specifications are governed by the Software Notice. By using and/or -copying this document, or the W3C document from which this statement is -linked,you (the licensee) agree that you have read, understood, and will comply -with the following terms and conditions: - -Permission to use, copy, and distribute the contents of this document, or theW3C -document from which this statement is linked, in any medium for any purpose and -without fee or royalty is hereby granted, provided that you include the -following on ALL copies of the document, or portions thereof, that you use: - -1. A link or URL to the original W3C document. -2. The pre-existing copyright notice of the original author, or if it doesn't -exist, a notice of the form: "Copyright [$date-of-document] World Wide Web -Consortium, (Massachusetts Institute of Technology, Institut National -deRecherche en Informatique et en Automatique, Keio University). All Rights -Reserved. http://www.w3.org/Consortium/Legal/" (Hypertext is preferred, but a -textual representation is permitted.) -3. If it exists, the STATUS of the W3C document. - -When space permits, inclusion of the full text of this NOTICE should be -provided. We request that authorship attribution be provided in any -software,documents, or other items or products that you create pursuant to the -implementation of the contents of this document, or any portion thereof. - -No right to create modifications or derivatives of W3C documents is granted -pursuant to this license. However, if additional requirements (documented in -the Copyright FAQ) are satisfied, the right to create modifications or -derivatives is sometimes granted by the W3C to individuals complying with those -requirements. - -THIS DOCUMENT IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO -REPRESENTATIONSOR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -WARRANTIES OFMERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, -NON-INFRINGEMENT, OR TITLE;THAT THE CONTENTS OF THE DOCUMENT ARE SUITABLE FOR -ANY PURPOSE; NOR THAT THEIMPLEMENTATION OF SUCH CONTENTS WILL NOT INFRINGE ANY -THIRD PARTY PATENTS,COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. - -COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL -ORCONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE DOCUMENT OR THE -PERFORMANCEOR IMPLEMENTATION OF THE CONTENTS THEREOF. - -The name and trademarks of copyright holders may NOT be used in advertising or -publicity pertaining to this document or its contents without specific, written -prior permission. Title to copyright in this document will at all times remain -with copyright holders. - ----------------------------------------------------------------------------- - -This formulation of W3C's notice and license became active on April 05 1999 so -as to account for the treatment of DTDs, schema's and bindings. See the older -formulation for the policy prior to this date. Please see our Copyright FAQ for -common questions about using materials from our site, including specific terms -and conditions for packages like libwww, Amaya, and Jigsaw. Other questions -about this notice can be directed to site-policy@w3.org. webmaster (last -updated by reagle on 1999/04/99.) - -%% The following software may be included in this product: Stax API; Use of any -of this software is governed by the terms of the license below: - -Streaming API for XML (JSR-173) Specification -Reference Implementation -License Agreement - -READ THE TERMS OF THIS (THE "AGREEMENT") CAREFULLY BEFORE VIEWING OR USING -THESOFTWARE LICENSED HEREUNDER. BY VIEWING OR USING THE SOFTWARE, YOU AGREE TO -THE TERMS OF THISAGREEMENT. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, -INDICATE YOUR ACCEPTANCE OF THESETERMS BY SELECTING THE "ACCEPT" BUTTON AT THE -END OF THIS AGREEMENT. IF YOU DO NOT AGREE TOALL THESE TERMS, PROMPTLY RETURN -THE UNUSED SOFTWARE TO ORIGINAL CONTRIBUTOR, DEFINED HEREIN. - -1.0 DEFINITIONS. - -1.1. "BEA" means BEA Systems, Inc., the licensor of the Original Code. - -1.2. "Contributor" means BEA and each entity that creates or contributes to the -creation of Modifications. - -1.3. "Covered Code" means the Original Code or Modifications or the combination -of the Original Code and Modifications, in each case including portions thereof -and corresponding documentation released with the source code. - -1.4. "Executable" means Covered Code in any form other than Source Code. - -1.5. "FCS" means first commercial shipment of a product. - -1.6. "Modifications" means any addition to or deletion from the substance or -structure of either the Original Code or any previous Modifications. When -Covered Code is released as a series of files, a Modification is: - -(a) Any addition to or deletion from the contents of a file containing Original -Code or previous Modifications. - -(b) Any new file that contains any part of the Original Code or previous -Modifications. - -1.7. "Original Code" means Source Code of computer software code Reference -Implementation. - -1.8. "Patent Claims" means any patent claim(s), now owned or hereafter -acquired,including without limitation, method, process, and apparatus claims, in -any patent for which the grantor has the right to grant a license. - -1.9. "Reference Implementation" means the prototype or "proof of -concept"implementation of the Specification developed and made available for -license by or on behalf of BEA. - -1.10. "Source Code" means the preferred form of the Covered Code for making -modifications to it, including all modules it contains, plus any associated -documentation,interface definition files, scripts used to control compilation -and installation of an Executable, or source code differential comparisons -against either the Original Code or another well known,available Covered Code of -the Contributor's choice. - -1.11. "Specification" means the written specification for the Streaming API for -XML , Java technology developed pursuant to the Java Community Process. - -1.12. "Technology Compatibility Kit" or "TCK" means the documentation, testing -tools and test suites associated with the Specification as may be revised by BEA -from time to time, that is provided so that an implementer of the Specification -may determine if its implementation is compliant with the Specification. - -1.13. "You" (or "Your") means an individual or a legal entity exercising rights -under, and complying with all of the terms of, this Agreement or a future -version of this Agreement issued under Section 6.1. For legal entities, "You" -includes any entity which controls,is controlled by, or is under common control -with You. For purposes of this definition,"control" means (a) the power, direct -or indirect, to cause the direction or management of such entity, whether by -contract or otherwise, or (b) ownership of more than fifty percent (50%) of the -outstanding shares or beneficial ownership of such entity. - -2.0 SOURCE CODE LICENSE. - -2.1. Copyright Grant. Subject to the terms of this Agreement, each Contributor -hereby grants You a non-exclusive, worldwide, royalty-free copyright license to -reproduce,prepare derivative works of, publicly display, publicly perform, -distribute and sublicense the Covered Code of such Contributor, if any, and such -derivative works, in Source Code and Executable form. - -2.2. Patent Grant. Subject to the terms of this Agreement, each Contributor -hereby grants You a non-exclusive, worldwide, royalty-free patent license under -the Patent Claims to make, use, sell, offer to sell, import and otherwise -transfer the Covered Code prepared and provided by such Contributor, if any, in -Source Code and Executable form. This patent license shall apply to the Covered -Code if, at the time a Modification is added by the Contributor,such addition of -the Modification causes such combination to be covered by the Patent Claims. -The patent license shall not apply to any other combinations which include the -Modification. - -2.3. Conditions to Grants. You understand that although each Contributor -grants the licenses to the Covered Code prepared by it, no assurances are -provided by any Contributor that the Covered Code does not infringe the patent -or other intellectual property rights of any other entity. Each Contributor -disclaims any liability to You for claims brought by any other entity based on -infringement of intellectual property rights or otherwise. As a condition to -exercising the rights and licenses granted hereunder, You hereby assume sole -responsibility to secure any other intellectual property rights needed, if any. -For example, if a thirdparty patent license is required to allow You to -distribute Covered Code, it is Your responsibility to acquire that license -before distributing such code. - -2.4. Contributors' Representation. Each Contributor represents that to its -knowledge it has sufficient copyright rights in the Covered Code it provides , -if any, to grant the copyright license set forth in this Agreement. - -3.0 DISTRIBUION RESTRICTIONS. - -3.1. Application of Agreement. - -The Modifications which You create or to which You contribute are governed by -the terms of this Agreement, including without limitation Section 2.0. The -Source Code version of Covered Code may be distributed only under the terms of -this Agreement or a future version of this Agreement released under Section 6.1, -and You must include a copy of this Agreement with every copy of the Source Code -You distribute. You may not offer or impose any terms on any Source Code -version that alters or restricts the applicable version of this Agreement or the -recipients' rights hereunder. However, You may include an additional document -offering the additional rights described in Section 3.3. - -3.2. Description of Modifications. - -You must cause all Covered Code to which You contribute to contain a file -documenting the changes You made to create that Covered Code and the date of any -change. You must include a prominent statement that the Modification is -derived, directly or indirectly, from Original Code provided by BEA and -including the name of BEA in (a) the Source Code, and (b) in any notice in an -Executable version or related documentation in which You describe the origin or -ownership of the Covered Code. - -%% The following software may be included in this product: X Window System; Use -of any of this software is governed by the terms of the license below: -Copyright The Open Group - -Permission to use, copy, modify, distribute, and sell this software and its -documentation for any purpose is hereby granted without fee, provided that the -above copyright notice appear in all copies and that both that copyright notice -and this permission notice appear in supporting documentation. - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESSFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE OPEN -GROUPBE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OFCONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH -THESOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Except as contained in this notice, the name of The Open Group shall not be used -in advertising or otherwise to promote the sale, use or other dealings in this -Software without prior written authorization from The Open Group. - -Portions also covered by other licenses as noted in the above URL. - -%% The following software may be included in this product: dom4j v. 1.6; Use -of any of this software is governed by the terms of the license below: - -Redistribution and use of this software and associated documentation -("Software"), with or without modification, are permitted provided that the -following conditions are met: - -1. Redistributions of source code must retain copyright statements and notices -Redistributions must also contain a copy of this document. - -2. Redistributions in binary form must reproduce the above copyright -notice,this list of conditions and the following disclaimer in the documentation -and/or other materials provided with the distribution. - -3. The name "DOM4J" must not be used to endorse or promote products derived -from this Software without prior written permission of MetaStuff, Ltd. For -written permission, please contact dom4j-info@metastuff.com. - -4. Products derived from this Software may not be called "DOM4J" nor may"DOM4J" -appear in their names without prior written permission of MetaStuff,Ltd. DOM4J -is a registered trademark of MetaStuff, Ltd. - -5. Due credit should be given to the DOM4J Project - http://www.dom4j.org - -THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS ``AS IS'' AND -ANYEXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -AREDISCLAIMED. IN NO EVENT SHALL METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE -FORANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -AND ONANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THISSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -Copyright 2001-2005 (C) MetaStuff, Ltd. All Rights Reserved. - -%% The following software may be included in this product: Retroweaver; Use of -any of this software is governed by the terms of the license below: - -Copyright (c) February 2004, Toby Reyelts All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -Redistributions of source code must retain the above copyright notice, this list -of conditions and the following disclaimer. Redistributions in binary form must -reproduce the above copyright notice, this list of conditions and the following -disclaimer in the documentation and/or other materials provided with the -distribution. Neither the name of Toby Reyelts nor the names of his -contributors may be used to endorse or promote products derived from this -software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICTLIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -%% The following software may be included in this product: stripper; Use of any -of this software is governed by the terms of the license below: - -Stripper : debug information stripper Copyright (c) 2003 Kohsuke Kawaguchi All -rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. Neither the name of the copyright holders nor the names of its contributors -may be used to endorse or promote products derived from this software without -specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -%% The following software may be included in this product: libpng official PNG -reference library; Use of any of this software is governed by the terms of the -license below: - -This copy of the libpng notices is provided for your convenience. In case of -any discrepancy between this copy and the notices in the file png.h that is -included in the libpng distribution, the latter shall prevail. - -COPYRIGHT NOTICE, DISCLAIMER, and LICENSE: - -If you modify libpng you may insert additional notices immediately following -this sentence. - -libpng version 1.2.6, December 3, 2004, is Copyright (c) 2004 Glenn - Randers-Pehrson, and is distributed according to the same disclaimer and - license as libpng-1.2.5with the following individual added to the list of - Contributing Authors Cosmin Truta - -libpng versions 1.0.7, July 1, 2000, through 1.2.5 - October 3, 2002, are - Copyright (c) 2000-2002 Glenn Randers-Pehrson, and are distributed according - to the same disclaimer and license as libpng-1.0.6 with the following - individuals added to the list of Contributing Authors Simon-Pierre Cadieux - Eric S. Raymond Gilles Vollant - -and with the following additions to the disclaimer: - -There is no warranty against interference with your enjoyment of the library or -against infringement. There is no warranty that our efforts or the library will -fulfill any of your particular purposes or needs. This library is provided with -all faults, and the entire risk of satisfactory quality, performance, accuracy, -and effort is with the user. - -libpng versions 0.97, January 1998, through 1.0.6, March 20, 2000, are Copyright - (c) 1998, 1999 Glenn Randers-Pehrson, and are distributed according to the - same disclaimer and license as libpng-0.96,with the following individuals - added to the list of Contributing Authors: Tom Lane Glenn Randers-Pehrson - Willem van Schaik - -libpng versions 0.89, June 1996, through 0.96, May 1997, are Copyright (c) 1996, -1997 Andreas Dilger Distributed according to the same disclaimer and license as -libpng-0.88, with the following individuals added to the list of Contributing -Authors: John Bowler Kevin Bracey Sam Bushell Magnus Holmgren Greg Roelofs Tom -Tanner - -libpng versions 0.5, May 1995, through 0.88, January 1996, are Copyright (c) -1995, 1996 Guy Eric Schalnat, Group 42, Inc. - -For the purposes of this copyright and license, "Contributing Authors"is defined -as the following set of individuals: - - Andreas Dilger - Dave Martindale - Guy Eric Schalnat - Paul Schmidt - Tim Wegner - -The PNG Reference Library is supplied "AS IS". The Contributing Authors and -Group 42, Inc. disclaim all warranties, expressed or implied, including, -without limitation, the warranties of merchantability and of fitness for any -purpose. The Contributing Authors and Group 42, Inc. assume no liability for -direct, indirect, incidental, special, exemplary,or consequential damages, which -may result from the use of the PNG Reference Library, even if advised of the -possibility of such damage. - -Permission is hereby granted to use, copy, modify, and distribute this source -code, or portions hereof, for any purpose, without fee, subject to the following -restrictions: - -1. The origin of this source code must not be misrepresented. - -2. Altered versions must be plainly marked as such and must not be -misrepresented as being the original source. - -3. This Copyright notice may not be removed or altered from any source or -altered source distribution. - -The Contributing Authors and Group 42, Inc. specifically permit, without fee, -and encourage the use of this source code as a component to supporting the PNG -file format in commercial products. If you use this source code in a product, -acknowledgment is not required but would be appreciated. - - -A "png_get_copyright" function is available, for convenient use in "about"boxes -and the like: - - printf("%s",png_get_copyright(NULL)); - -Also, the PNG logo (in PNG format, of course) is supplied in the files -"pngbar.png" and "pngbar.jpg (88x31) and "pngnow.png" (98x31). - -Libpng is OSI Certified Open Source Software. OSI Certified Open Source is a -certification mark of the Open Source Initiative. - -Glenn Randers-Pehrson -glennrp at users.sourceforge.net -December 3, 2004 - -%% The following software may be included in this product: Libungif - An -uncompressed GIF library; Use of any of this software is governed by the terms -of the license below: -The GIFLIB distribution is Copyright (c) 1997 Eric S.Raymond - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO -EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR -OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS -INTHE SOFTWARE. - - -%% The following software may be included in this product: Ant; Use of any of -this software is governed by the terms of the license below: License The Apache -Software License Version 2.0 - -The Apache Software License Version 2.0 applies to all releases of Ant starting -with ant 1.6.1 - -/* - * Apache License - * Version 2.0, January 2004 - * http://www.apache.org/licenses/ - * - * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - * - * 1. Definitions. - * - * "License" shall mean the terms and conditions for use, reproduction, - * and distribution as defined by Sections 1 through 9 of this document. - * - * "Licensor" shall mean the copyright owner or entity authorized by - * the copyright owner that is granting the License. - * - * "Legal Entity" shall mean the union of the acting entity and all - * other entities that control, are controlled by, or are under common - * control with that entity. For the purposes of this definition, - * "control" means (i) the power, direct or indirect, to cause the - * direction or management of such entity, whether by contract or - * otherwise, or (ii) ownership of fifty percent (50%) or more of the - * outstanding shares, or (iii) beneficial ownership of such entity. - * - * "You" (or "Your") shall mean an individual or Legal Entity - * exercising permissions granted by this License. - * - * "Source" form shall mean the preferred form for making modifications, - * including but not limited to software source code, documentation - * source, and configuration files. - * - * "Object" form shall mean any form resulting from mechanical - * transformation or translation of a Source form, including but - * not limited to compiled object code, generated documentation, - * and conversions to other media types. - * - * "Work" shall mean the work of authorship, whether in Source or - * Object form, made available under the License, as indicated by a - * copyright notice that is included in or attached to the work - * (an example is provided in the Appendix below). - * - * "Derivative Works" shall mean any work, whether in Source or Object - * form, that is based on (or derived from) the Work and for which the - * editorial revisions, annotations, elaborations, or other modifications - * represent, as a whole, an original work of authorship. For the purposes - * of this License, Derivative Works shall not include works that remain - * separable from, or merely link (or bind by name) to the interfaces of, - * the Work and Derivative Works thereof. - * - * "Contribution" shall mean any work of authorship, including - * the original version of the Work and any modifications or additions - * to that Work or Derivative Works thereof, that is intentionally - * submitted to Licensor for inclusion in the Work by the copyright owner - * or by an individual or Legal Entity authorized to submit on behalf of - * the copyright owner. For the purposes of this definition, "submitted" - * means any form of electronic, verbal, or written communication sent - * to the Licensor or its representatives, including but not limited to - * communication on electronic mailing lists, source code control systems, - * and issue tracking systems that are managed by, or on behalf of, the - * Licensor for the purpose of discussing and improving the Work, but - * excluding communication that is conspicuously marked or otherwise - * designated in writing by the copyright owner as "Not a Contribution." - * - * "Contributor" shall mean Licensor and any individual or Legal Entity - * on behalf of whom a Contribution has been received by Licensor and - * subsequently incorporated within the Work. - * - * 2. Grant of Copyright License. Subject to the terms and conditions of - * this License, each Contributor hereby grants to You a perpetual, - * worldwide, non-exclusive, no-charge, royalty-free, irrevocable - * copyright license to reproduce, prepare Derivative Works of, - * publicly display, publicly perform, sublicense, and distribute the - * Work and such Derivative Works in Source or Object form. - * - * 3. Grant of Patent License. Subject to the terms and conditions of - * this License, each Contributor hereby grants to You a perpetual, - * worldwide, non-exclusive, no-charge, royalty-free, irrevocable - * (except as stated in this section) patent license to make, have made, - * use, offer to sell, sell, import, and otherwise transfer the Work, - * where such license applies only to those patent claims licensable - * by such Contributor that are necessarily infringed by their - * Contribution(s) alone or by combination of their Contribution(s) - * with the Work to which such Contribution(s) was submitted. If You - * institute patent litigation against any entity (including a - * cross-claim or counterclaim in a lawsuit) alleging that the Work - * or a Contribution incorporated within the Work constitutes direct - * or contributory patent infringement, then any patent licenses - * granted to You under this License for that Work shall terminate - * as of the date such litigation is filed. - * - * 4. Redistribution. You may reproduce and distribute copies of the - * Work or Derivative Works thereof in any medium, with or without - * modifications, and in Source or Object form, provided that You - * meet the following conditions: - * - * (a) You must give any other recipients of the Work or - * Derivative Works a copy of this License; and - * - * (b) You must cause any modified files to carry prominent notices - * stating that You changed the files; and - * - * (c) You must retain, in the Source form of any Derivative Works - * that You distribute, all copyright, patent, trademark, and - * attribution notices from the Source form of the Work, - * excluding those notices that do not pertain to any part of - * the Derivative Works; and - * - * (d) If the Work includes a "NOTICE" text file as part of its - * distribution, then any Derivative Works that You distribute must - * include a readable copy of the attribution notices contained - * within such NOTICE file, excluding those notices that do not - * pertain to any part of the Derivative Works, in at least one - * of the following places: within a NOTICE text file distributed - * as part of the Derivative Works; within the Source form or - * documentation, if provided along with the Derivative Works; or, - * within a display generated by the Derivative Works, if and - * wherever such third-party notices normally appear. The contents - * of the NOTICE file are for informational purposes only and - * do not modify the License. You may add Your own attribution - * notices within Derivative Works that You distribute, alongside - * or as an addendum to the NOTICE text from the Work, provided - * that such additional attribution notices cannot be construed - * as modifying the License. - * - * You may add Your own copyright statement to Your modifications and - * may provide additional or different license terms and conditions - * for use, reproduction, or distribution of Your modifications, or - * for any such Derivative Works as a whole, provided Your use, - * reproduction, and distribution of the Work otherwise complies with - * the conditions stated in this License. - * - * 5. Submission of Contributions. Unless You explicitly state otherwise, - * any Contribution intentionally submitted for inclusion in the Work - * by You to the Licensor shall be under the terms and conditions of - * this License, without any additional terms or conditions. - * Notwithstanding the above, nothing herein shall supersede or modify - * the terms of any separate license agreement you may have executed - * with Licensor regarding such Contributions. - * - * 6. Trademarks. This License does not grant permission to use the trade - * names, trademarks, service marks, or product names of the Licensor, - * except as required for reasonable and customary use in describing the - * origin of the Work and reproducing the content of the NOTICE file. - * - * 7. Disclaimer of Warranty. Unless required by applicable law or - * agreed to in writing, Licensor provides the Work (and each - * Contributor provides its Contributions) on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - * implied, including, without limitation, any warranties or conditions - * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - * PARTICULAR PURPOSE. You are solely responsible for determining the - * appropriateness of using or redistributing the Work and assume any - * risks associated with Your exercise of permissions under this License. - * - * 8. Limitation of Liability. In no event and under no legal theory, - * whether in tort (including negligence), contract, or otherwise, - * unless required by applicable law (such as deliberate and grossly - * negligent acts) or agreed to in writing, shall any Contributor be - * liable to You for damages, including any direct, indirect, special, - * incidental, or consequential damages of any character arising as a - * result of this License or out of the use or inability to use the - * Work (including but not limited to damages for loss of goodwill, - * work stoppage, computer failure or malfunction, or any and all - * other commercial damages or losses), even if such Contributor - * has been advised of the possibility of such damages. - * - * 9. Accepting Warranty or Additional Liability. While redistributing - * the Work or Derivative Works thereof, You may choose to offer, - * and charge a fee for, acceptance of support, warranty, indemnity, - * or other liability obligations and/or rights consistent with this - * License. However, in accepting such obligations, You may act only - * on Your own behalf and on Your sole responsibility, not on behalf - * of any other Contributor, and only if You agree to indemnify, - * defend, and hold each Contributor harmless for any liability - * incurred by, or claims asserted against, such Contributor by reason - * of your accepting any such warranty or additional liability. - * - * END OF TERMS AND CONDITIONS - * - * APPENDIX: How to apply the Apache License to your work. - * - * To apply the Apache License to your work, attach the following - * boilerplate notice, with the fields enclosed by brackets "[]" - * replaced with your own identifying information. (Don't include - * the brackets!) The text should be enclosed in the appropriate - * comment syntax for the file format. We also recommend that a - * file or class name and description of purpose be included on the - * same "printed page" as the copyright notice for easier - * identification within third-party archives. - * - * Copyright [yyyy] Apache Software Foundation - * - * Licensed 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. - */ - - -You can download the original license file here. - -The License is accompanied by a NOTICE - - ========================================================================= - == NOTICE file corresponding to the section 4 d of == - == the Apache License, Version 2.0, == - == in this case for the Apache Ant distribution. == - ========================================================================= - This product includes software developed by - The Apache Software Foundation (http://www.apache.org/). - -This product includes also software developed by : - the W3C consortium - (http://www.w3c.org) , - the SAX project (http://www.saxproject.org) - -Please read the different LICENSE files present in the root directory of this -distribution. - -The names "Ant" and "Apache Software Foundation" must not be used to endorse or -promote products derived from this software without prior written permission. -For written permission, please contact apache@apache.org. - -The Apache Software License, Version 1.1 - -The Apache Software License, Version 1.1, applies to all versions of up to -ant1.6.0 included. - -/* - * ============================================================================ - * The Apache Software License, Version 1.1 - * ============================================================================ - * - * Copyright (C) 2000-2003 The Apache Software Foundation. All - * rights reserved. - * - * Redistribution and use in source and binary forms, with or without modifica- - * tion, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, - * this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * 3. The end-user documentation included with the redistribution, if any, must - * include the following acknowledgment: "This product includes software - * developed by the Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowledgment may appear in the software itself, if - * and wherever such third-party acknowledgments normally appear. - * - * 4. The names "Ant" and "Apache Software Foundation" must not be used to - * endorse or promote products derived from this software without prior - * written permission. For written permission, please contact - * apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache", nor may - * "Apache" appear in their name, without prior written permission of the - * Apache Software Foundation. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, - * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND - * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, - * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLU- - * DING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON - * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * This software consists of voluntary contributions made by many individuals - * on behalf of the Apache Software Foundation. For more information on the - * Apache Software Foundation, please see http://www.apache.org. - * - */ - - -%% The following software may be included in this product: XML Resolver -library; Use of any of this software is governed by the terms of the license -below: - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by the - copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all other - entities that control, are controlled by, or are under common control - with that entity. For the purposes of this definition, "control" means - (i) the power, direct or indirect, to cause the direction or management - of such entity, whether by contract or otherwise, or (ii) ownership of - fifty percent (50%) or more of the outstanding shares, or - (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity exercising - permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation source, - and configuration files. - - "Object" form shall mean any form resulting from mechanical transformation - or translation of a Source form, including but not limited to compiled - object code, generated documentation, and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or Object - form, made available under the License, as indicated by a copyright - notice that is included in or attached to the work (an example is - provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces - of, the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including the original - version of the Work and any modifications or additions to that Work or - Derivative Works thereof, that is intentionally submitted to Licensor - for inclusion in the Work by the copyright owner or by an individual - or Legal Entity authorized to submit on behalf of the copyright owner. - For the purposes of this definition, "submitted" means any form of - electronic, verbal, or written communication sent to the Licensor or - its representatives, including but not limited to communication on - electronic mailing lists, source code control systems, and issue - tracking systems that are managed by, or on behalf of, the Licensor - for the purpose of discussing and improving the Work, but excluding - communication that is conspicuously marked or otherwise designated - in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright - license to reproduce, prepare Derivative Works of, publicly display, - publicly perform, sublicense, and distribute the Work and such - Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of this - License, each Contributor hereby grants to You a perpetual, worldwide, - non-exclusive, no-charge, royalty-free, irrevocable (except as stated - in this section) patent license to make, have made, use, offer to sell, - sell, import, and otherwise transfer the Work, where such license - applies only to those patent claims licensable by such Contributor - that are necessarily infringed by their Contribution(s) alone or by - combination of their Contribution(s) with the Work to which such - Contribution(s) was submitted. If You institute patent litigation - against any entity (including a cross-claim or counterclaim in a - lawsuit) alleging that the Work or a Contribution incorporated within - the Work constitutes direct or contributory patent infringement, then - any patent licenses granted to You under this License for that Work - shall terminate as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the Work - or Derivative Works thereof in any medium, with or without modifications, - and in Source or Object form, provided that You meet the following - conditions: - - (a) You must give any other recipients of the Work or Derivative Works - a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works that - You distribute, all copyright, patent, trademark, and attribution notices - from the Source form of the Work, excluding those notices that do not - pertain to any part of the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its distribution, - then any Derivative Works that You distribute must include a readable copy - of the attribution notices contained within such NOTICE file, excluding - those notices that do not pertain to any part of the Derivative Works, in - at least one of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or documentation, - if provided along with the Derivative Works; or, within a display generated - by the Derivative Works, if and wherever such third-party notices normally - appear. The contents of the NOTICE file are for informational purposes only - and do not modify the License. You may add Your own attribution notices - within Derivative Works that You distribute, alongside or as an addendum to - the NOTICE text from the Work, provided that such additional attribution - notices cannot be construed as modifying the License. - - You may add Your own copyright statement to Your modifications and may provide - additional or different license terms and conditions for use, reproduction, - or distribution of Your modifications, or for any such Derivative Works as a - whole, provided Your use, reproduction, and distribution of the Work otherwise - complies with the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, any - Contribution intentionally submitted for inclusion in the Work by You to - the Licensor shall be under the terms and conditions of this License, without - any additional terms or conditions. Notwithstanding the above, nothing herein - shall supersede or modify the terms of any separate license agreement you may - have executed with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade names, - trademarks, service marks, or product names of the Licensor, except as required - for reasonable and customary use in describing the origin of the Work and - reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in - writing, Licensor provides the Work (and each Contributor provides its - Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF - ANY KIND, either express or implied, including, without limitation, any - warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or - FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining - the appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, whether - in tort (including negligence), contract, or otherwise, unless required by - applicable law (such as deliberate and grossly negligent acts) or agreed to - in writing, shall any Contributor be liable to You for damages, including - any direct, indirect, special, incidental, or consequential damages of any - character arising as a result of this License or out of the use or inability - to use the Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all other - commercial damages or losses), even if such Contributor has been advised - of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing the Work - or Derivative Works thereof, You may choose to offer, and charge a fee for, - acceptance of support, warranty, indemnity, or other liability obligations - and/or rights consistent with this License. However, in accepting such - obligations, You may act only on Your own behalf and on Your sole - responsibility, not on behalf of any other Contributor, and only if You - agree to indemnify, defend, and hold each Contributor harmless for any - liability incurred by, or claims asserted against, such Contributor by - reason of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following boilerplate notice, - with the fields enclosed by brackets "[]" replaced with your own identifying - information. (Don't include the brackets!) The text should be enclosed in the - appropriate comment syntax for the file format. We also recommend that a file - or class name and description of purpose be included on the same "printed page" - as the copyright notice for easier identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. - - -%% The following software may be included in this product: ICU4J; Use of any of -this software is governed by the terms of the license below: - -ICU License - ICU 1.8.1 and later COPYRIGHT AND PERMISSION NOTICE Copyright (c) - -1995-2003 International Business Machines Corporation and others All rights - reserved Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the "Software"), to - deal in the Software without restriction, including without limitation the - rights to use, copy, modify, merge, publish, distribute, and/or sell copies of - the Software, and to permit persons to whom the Software is furnished to do - so,provided that the above copyright notice(s) and this permission notice - appear in all copies of the Software and that both the above copyright - notice(s) and this permission notice appear in supporting documentation. THE - SOFTWARE IS PROVIDED"AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOTLIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - PARTICULAR PURPOSEAND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL - THE COPYRIGHTHOLDER OR HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, - OR ANYSPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER - RESULTINGFROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, - NEGLIGENCEOR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE - USE ORPERFORMANCE OF THIS SOFTWARE. Except as contained in this notice, the - name of a copyright holder shall not be used in advertising or otherwise to - promote the sale, use or other dealings in this Software without prior written - authorization of the copyright holder. - - -%% The following software may be included in this product: NekoHTML; Use of any -of this software is governed by the terms of the license below: The CyberNeko -Software License, Version 1.0 - - -(C) Copyright 2002,2003, Andy Clark. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in - the documentation and/or other materials provided with the - distribution. - -3. The end-user documentation included with the redistribution, - if any, must include the following acknowledgment: - "This product includes software developed by Andy Clark." - Alternately, this acknowledgment may appear in the software itself, - if and wherever such third-party acknowledgments normally appear. - -4. The names "CyberNeko" and "NekoHTML" must not be used to endorse - or promote products derived from this software without prior - written permission. For written permission, please contact - andy@cyberneko.net. - -5. Products derived from this software may not be called "CyberNeko", - nor may "CyberNeko" appear in their name, without prior written - permission of the author. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR -OR OTHER CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING -IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGE. - -==================================================================== -This license is based on the Apache Software License, version 1.1 - - -%% The following software may be included in this product: Jing; Use of any of -this software is governed by the terms of the license below: Jing Copying -Conditions - -Copyright (c) 2001-2003 Thai Open Source Software Center Ltd All rights -reserved. - -Redistribution and use in source and binary forms, with or without -modification,are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice,this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of the Thai Open Source Software Center Ltd nor the - names of its contributors may be used to endorse or promote products - derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -ANDANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIEDWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -AREDISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR -ANYDIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES;LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED -AND ONANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR -TORT(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF -THISSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -%% The following software may be included in this product: RelaxNGCC; Use of -any of this software is governed by the terms of the license below: - -Copyright (c) 2000-2003 Daisuke Okajima and Kohsuke Kawaguchi. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright -notice, this list of conditions and the following disclaimer in the -documentation and/or other materials provided with the distribution. - -3. The end-user documentation included with the redistribution, if -any, must include the following acknowledgment: - - "This product includes software developed by Daisuke Okajima - and Kohsuke Kawaguchi (http://relaxngcc.sf.net/)." - -Alternately, this acknowledgment may appear in the software itself, -if and wherever such third-party acknowledgments normally appear. - -4. The names of the copyright holders must not be used to endorse or -promote products derived from this software without prior written -permission. For written permission, please contact the copyright -holders. - -5. Products derived from this software may not be called "RELAXNGCC", -nor may "RELAXNGCC" appear in their name, without prior written -permission of the copyright holders. - -THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.IN NO EVENT SHALL THE APACHE -SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -%% The following software may be included in this product: RELAX NG Object -Model/Parser; Use of any of this software is governed by the terms of the -license below: The MIT License - -Copyright (c) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do -so,subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESSFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS ORCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHERIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR -INCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - -%% The following software may be included in this product: XFree86-VidMode -Extension; Use of any of this software is governed by the terms of the license -below: Version 1.1 of Project Licence. - - Copyright (C) 1994-2004 The Project, Inc. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicence, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do -so,subject to the following conditions: - -1. Redistributions of source code must retain the above copyright notice,this -list of conditions, and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution, and in the same place and form -as other copyright, license and disclaimer information. - -3. The end-user documentation included with the redistribution, if any,must -include the following acknowledgment: "This product includes software developed -by The XFree86 Project, Inc (http://www.xfree86.org/) and its contributors", in -the same place and form as other third-party acknowledgments. Alternately, this -acknowledgment may appear in the software itself, in the same form and location -as other such third-party acknowledgments. - -4. Except as contained in this notice, the name of The XFree86 Project,Inc -shall not be used in advertising or otherwise to promote the sale, use or other -dealings in this Software without prior written authorization from TheXFree86 -Project, Inc. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED -WARRANTIES,INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY ANDFITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -SHALL THE XFREE86PROJECT, INC OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL,SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; ORBUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER INCONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISINGIN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THE POSSIBILITYOF SUCH DAMAGE. - - -%% The following software may be included in this product: RelaxNGCC; Use of -any of this software is governed by the terms of the license below: This is -version 2003-May-08 of the Info-ZIP copyright and license. The definitive -version of this document should be available at -ftp://ftp.info-zip.org/pub/infozip/license.html indefinitely. - - -Copyright (c) 1990-2003 Info-ZIP. All rights reserved. - -For the purposes of this copyright and license, "Info-ZIP" is defined asthe -following set of individuals: - - Mark Adler, John Bush, Karl Davis, Harald Denker, Jean-Michel Dubois, Jean-loup - Gailly, Hunter Goatley, Ian Gorman, Chris Herborth, Dirk Haase, Greg Hartwig, - Robert Heath, Jonathan Hudson, Paul Kienitz, David Kirschbaum, Johnny Lee, - Onno van der Linden, Igor Mandrichenko, Steve P. Miller, Sergio Monesi, - Keith Owens, George Petrov, Greg Roelofs, Kai Uwe Rommel, Steve Salisbury, - Dave Smith, Christian Spieler, Antoine Verheijen, Paul von Behren, Rich - Wales, Mike White - -This software is provided "as is," without warranty of any kind, express or -implied. In no event shall Info-ZIP or its contributors be held liable for any -direct, indirect, incidental, special or consequential damages arising out of -the use of or inability to use this software. - -Permission is granted to anyone to use this software for any purpose, including -commercial applications, and to alter it and redistribute it freely, subject to -the following restrictions: - -1. Redistributions of source code must retain the above copyright notice, -definition, disclaimer, and this list of conditions. - -2. Redistributions in binary form (compiled executables) must reproduce the -above copyright notice, definition, disclaimer, and this list of conditions in -documentation and/or other materials provided with the distribution. The sole -exception to this condition is redistribution of a standard UnZipSFX binary -(including SFXWiz) as part of a self-extracting archive; that is permitted -without inclusion of this license, as long as the normal SFX banner has not been -removed from the binary or disabled. - -3. Altered versions--including, but not limited to, ports to new operating -systems, existing ports with new graphical interfaces, and dynamic, shared, or -static library versions--must be plainly marked as such and must not be -misrepresented as being the original source. Such altered versions also must -not be misrepresented as being Info-ZIP releases--including, but not limited to, -labeling of the altered versions with the names "Info-ZIP" (or any variation -thereof, including, but not limited to, different capitalizations), "Pocket -UnZip," "WiZ" or "MacZip" without the explicit permission of Info-ZIP. Such -altered versions are further prohibited from misrepresentative use of the -Zip-Bugs or Info-ZIP e-mail addresses or of the Info-ZIP URL(s). - -4. Info-ZIP retains the right to use the names "Info-ZIP," "Zip," "UnZip," -"UnZipSFX," "WiZ," "Pocket UnZip," "Pocket Zip," and "MacZip" for its own source -and binary releases. - - -%% The following software may be included in this product: XML Security; Use of - any of this software is governed by the terms of the license below: The - Apache Software License, Version 1.1 PDF - -Copyright (C) 2002 The Apache Software Foundation. - -All rights reserved. Redistribution and use in source and binary forms, with or -without modifica- tion, are permitted provided that the following conditions are -met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. The end-user documentation included with the redistribution, if any,must -include the following acknowledgment:"This product includes software developed -by the Apache Software Foundation (http://www.apache.org/)." Alternately, this -acknowledgment may appear in the software itself, if and wherever such -third-party acknowledgments normally appear. - -4. The names"Apache Forrest" and "Apache Software Foundation" must not be used -to endorse or promote products derived from this software without prior written -permission. For written permission,please contact apache@apache.org. 5. -Products derived from this software may not be called "Apache", normay "Apache" -appear in their name, without prior written permission of the Apache Software -Foundation. THIS SOFTWARE IS PROVIDED``AS IS'' AND ANY EXPRESSED OR IMPLIED -WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO -EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY -DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ORSERVICES; LOSS -OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANYTHEORY OF LIABILITY, WHETHER IN CONTRACT, STRICTLIABILITY, OR TORT (INCLUDING -NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, -EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This software consists of -voluntary contributions made by many individuals on behalf of the Apache -Software Foundation. For more information on the Apache Software Foundation, -please see http://www.apache.org. - - -%% The following software may be included in this product: Regexp, Regular -Expression Package v. 1.2; Use of any of this software is governed by the terms -of the license below: The Apache Software License, Version 1.1 Copyright (c) -2001 The Apache Software Foundation. All rights reserved. Redistribution and -use in source and binary forms, with or without modification,are permitted -provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -3. The end-user documentation included with the redistribution, if any, must -include the following acknowledgment: "This product includes software developed -by the Apache Software Foundation (http://www.apache.org/)." Alternately, this -acknowledgment may appear in the software itself, if and wherever such -third-party acknowledgments normally appear. - -4. The names "Apache" and "Apache Software Foundation" and "Apache Turbine" -must not be used to endorse or promote products derived from this software -without prior written permission. For written permission, please contact -apache@apache.org. - -5. Products derived from this software may not be called "Apache", "Apache -Turbine", nor may "Apache" appear in their name, without prior written -permission of the Apache Software Foundation. - -THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, -INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND -FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE -SOFTWARE FOUNDATION OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF -ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - - -This software consists of voluntary contributions made by many individuals on -behalf of the Apache Software Foundation. For more information on the Apache -Software Foundation, please see http://www.apache.org. - -======================================================================== - - -%% The following software may be included in this product: zlib; Use of any of -this software is governed by the terms of the license below: - -zlib.h -- interface of the 'zlib' general purpose compression library - version 1.1.3, July 9th, 1998 - - Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler - - This software is provided 'as-is', without any express or implied - warranty. In no event will the authors be held liable for any damages - arising from the use of this software. - - Permission is granted to anyone to use this software for any purpose, - including commercial applications, and to alter it and redistribute it - freely, subject to the following restrictions: - - 1. The origin of this software must not be misrepresented; you must not - claim that you wrote the original software. If you use this software - in a product, an acknowledgment in the product documentation would be - appreciated but is not required. - 2. Altered source versions must be plainly marked as such, and must not be - misrepresented as being the original software. - 3. This notice may not be removed or altered from any source distribution. - - Jean-loup Gailly Mark Adler - jloup@gzip.org madler@alumni.caltech.edu - - - The data format used by the zlib library is described by RFCs (Request for - Comments) 1950 to 1952 in the files ftp://ds.internic.net/rfc/rfc1950.txt - (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format - - -%% The following software may be included in this product: Mozilla Rhino. Use -of any of this software is governed by the terms of the license below: - - * The contents of this file are subject to the Netscape Public - * License Version 1.1 (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.mozilla.org/NPL/ - * - * Software distributed under the License is distributed on an "AS - * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or - * implied. See the License for the specific language governing - * rights and limitations under the License. - * - * The Original Code is Rhino code, released - * May 6, 1999. - * - * The Initial Developer of the Original Code is Netscape - * Communications Corporation. Portions created by Netscape are - * Copyright (C) 1997-2000 Netscape Communications Corporation. All - * Rights Reserved. - * - * Contributor(s): - * - * Kemal Bayram - * Patrick Beard - * Norris Boyd - * Igor Bukanov, igor@mir2.org - * Brendan Eich - * Ethan Hugg - * Roger Lawrence - * Terry Lucas - * Mike McCabe - * Milen Nankov - * Attila Szegedi, szegedia@freemail.hu - * Ian D. Stewart - * Andi Vajda - * Andrew Wason - */ - -%% The following software may be included in this product: Apache Derby. Use -of any of this software is governed by the terms of the license below: - - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed 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. - - -%% The following software may be included in this product: 7-Zip. Use of any -of this software is governed by the terms of the license below: - - ~~~~~ - License for use and distribution - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - 7-Zip Copyright (C) 1999-2007 Igor Pavlov. - - Licenses for files are: - - 1) 7z.dll: GNU LGPL + AES code license + unRAR restriction - 2) 7za.exe, 7z.sfx and 7zCon.sfx: GNU LGPL + AES code license - 3) All other files: GNU LGPL - - The GNU LGPL + AES code license + unRAR restriction means that you must follow - GNU LGPL rules, AES code license rules and unRAR restriction rules. - - The GNU LGPL + AES code license means that you must follow both GNU LGPL rules - and AES code license rules. - - - Note: - You can use 7-Zip on any computer, including a computer in a commercial - organization. You don't need to register or pay for 7-Zip. - - - GNU LGPL information - -------------------- - -GNU Lesser General Public License - -Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - [This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - -Preamble - -The licenses for most software are designed to take away your freedom to share -and change it. By contrast, the GNU General Public Licenses are intended to -guarantee your freedom to share and change free software--to make sure the -software is free for all its users. - -This license, the Lesser General Public License, applies to some specially -designated software packages--typically libraries--of the Free Software -Foundation and other authors who decide to use it. You can use it too, but we -suggest you first think carefully about whether this license or the ordinary -General Public License is the better strategy to use in any particular case, -based on the explanations below. - -When we speak of free software, we are referring to freedom of use, not price. -Our General Public Licenses are designed to make sure that you have the freedom -to distribute copies of free software (and charge for this service if you wish); -that you receive source code or can get it if you want it; that you can change -the software and use pieces of it in new free programs; and that you are -informed that you can do these things. - -To protect your rights, we need to make restrictions that forbid distributors to -deny you these rights or to ask you to surrender these rights. These -restrictions translate to certain responsibilities for you if you distribute -copies of the library or if you modify it. - -For example, if you distribute copies of the library, whether gratis or for a -fee, you must give the recipients all the rights that we gave you. You must -make sure that they, too, receive or can get the source code. If you link other -code with the library, you must provide complete object files to the recipients, -so that they can relink them with the library after making changes to the -library and recompiling it. And you must show them these terms so they know -their rights. - -We protect your rights with a two-step method: (1) we copyright the library, -and (2) we offer you this license, which gives you legal permission to copy, -distribute and/or modify the library. - -To protect each distributor, we want to make it very clear that there is no -warranty for the free library. Also, if the library is modified by someone else -and passed on, the recipients should know that what they have is not the -original version, so that the original author's reputation will not be affected -by problems that might be introduced by others. - -Finally, software patents pose a constant threat to the existence of any free -program. We wish to make sure that a company cannot effectively restrict the -users of a free program by obtaining a restrictive license from a patent holder. -Therefore, we insist that any patent license obtained for a version of the -library must be consistent with the full freedom of use specified in this -license. - -Most GNU software, including some libraries, is covered by the ordinary GNU -General Public License. This license, the GNU Lesser General Public License, -applies to certain designated libraries, and is quite different from the -ordinary General Public License. We use this license for certain libraries in -order to permit linking those libraries into non-free programs. - -When a program is linked with a library, whether statically or using a shared -library, the combination of the two is legally speaking a combined work, a -derivative of the original library. The ordinary General Public License -therefore permits such linking only if the entire combination fits its criteria -of freedom. The Lesser General Public License permits more lax criteria for -linking other code with the library. - -We call this license the "Lesser" General Public License because it does Less to -protect the user's freedom than the ordinary General Public License. It also -provides other free software developers Less of an advantage over competing -non-free programs. These disadvantages are the reason we use the ordinary -General Public License for many libraries. However, the Lesser license provides -advantages in certain special circumstances. - -For example, on rare occasions, there may be a special need to encourage the -widest possible use of a certain library, so that it becomes a de-facto -standard. To achieve this, non-free programs must be allowed to use the -library. A more frequent case is that a free library does the same job as -widely used non-free libraries. In this case, there is little to gain by -limiting the free library to free software only, so we use the Lesser General -Public License. - -In other cases, permission to use a particular library in non-free programs -enables a greater number of people to use a large body of free software. For -example, permission to use the GNU C Library in non-free programs enables many -more people to use the whole GNU operating system, as well as its variant, the -GNU/Linux operating system. - -Although the Lesser General Public License is Less protective of the users' -freedom, it does ensure that the user of a program that is linked with the -Library has the freedom and the wherewithal to run that program using a modified -version of the Library. - -The precise terms and conditions for copying, distribution and modification -follow. Pay close attention to the difference between a "work based on the -library" and a "work that uses the library". The former contains code derived -from the library, whereas the latter must be combined with the library in order -to run. TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - -0. This License Agreement applies to any software library or other program -which contains a notice placed by the copyright holder or other authorized party -saying it may be distributed under the terms of this Lesser General Public -License (also called "this License"). Each licensee is addressed as "you". - -A "library" means a collection of software functions and/or data prepared so as -to be conveniently linked with application programs (which use some of those -functions and data) to form executables. - -The "Library", below, refers to any such software library or work which has been -distributed under these terms. A "work based on the Library" means either the -Library or any derivative work under copyright law: that is to say, a work -containing the Library or a portion of it, either verbatim or with modifications -and/or translated straightforwardly into another language. (Hereinafter, -translation is included without limitation in the term "modification".) - -"Source code" for a work means the preferred form of the work for making -modifications to it. For a library, complete source code means all the source -code for all modules it contains, plus any associated interface definition -files, plus the scripts used to control compilation and installation of the -library. - -Activities other than copying, distribution and modification are not covered by -this License; they are outside its scope. The act of running a program using -the Library is not restricted, and output from such a program is covered only if -its contents constitute a work based on the Library (independent of the use of -the Library in a tool for writing it). Whether that is true depends on what the -Library does and what the program that uses the Library does. - -1. You may copy and distribute verbatim copies of the Library's complete source -code as you receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice and -disclaimer of warranty; keep intact all the notices that refer to this License -and to the absence of any warranty; and distribute a copy of this License along -with the Library. - -You may charge a fee for the physical act of transferring a copy, and you may at -your option offer warranty protection in exchange for a fee. - -2. You may modify your copy or copies of the Library or any portion of it, thus -forming a work based on the Library, and copy and distribute such modifications -or work under the terms of Section 1 above, provided that you also meet all of -these conditions: - -a) The modified work must itself be a software library. - -b) You must cause the files modified to carry prominent notices stating that you -changed the files and the date of any change. - -c) You must cause the whole of the work to be licensed at no charge to all third -parties under the terms of this License. - -d) If a facility in the modified Library refers to a function or a table of data -to be supplied by an application program that uses the facility, other than as -an argument passed when the facility is invoked, then you must make a good faith -effort to ensure that, in the event an application does not supply such function -or table, the facility still operates, and performs whatever part of its purpose -remains meaningful. - -(For example, a function in a library to compute square roots has a purpose that -is entirely well-defined independent of the application. Therefore, Subsection -2d requires that any application-supplied function or table used by this -function must be optional: if the application does not supply it, the square -root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If identifiable -sections of that work are not derived from the Library, and can be reasonably -considered independent and separate works in themselves, then this License, and -its terms, do not apply to those sections when you distribute them as separate -works. But when you distribute the same sections as part of a whole which is a -work based on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the entire whole, -and thus to each and every part regardless of who wrote it. - -Thus, it is not the intent of this section to claim rights or contest your -rights to work written entirely by you; rather, the intent is to exercise the -right to control the distribution of derivative or collective works based on the -Library. - -In addition, mere aggregation of another work not based on the Library with the -Library (or with a work based on the Library) on a volume of a storage or -distribution medium does not bring the other work under the scope of this -License. - -3. You may opt to apply the terms of the ordinary GNU General Public License -instead of this License to a given copy of the Library. To do this, you must -alter all the notices that refer to this License, so that they refer to the -ordinary GNU General Public License, version 2, instead of to this License. (If -a newer version than version 2 of the ordinary GNU General Public License has -appeared, then you can specify that version instead if you wish.) Do not make -any other change in these notices. - -Once this change is made in a given copy, it is irreversible for that copy, so -the ordinary GNU General Public License applies to all subsequent copies and -derivative works made from that copy. - -This option is useful when you wish to copy part of the code of the Library into -a program that is not a library. - -4. You may copy and distribute the Library (or a portion or derivative of it, -under Section 2) in object code or executable form under the terms of Sections 1 -and 2 above provided that you accompany it with the complete corresponding -machine-readable source code, which must be distributed under the terms of -Sections 1 and 2 above on a medium customarily used for software interchange. - -If distribution of object code is made by offering access to copy from a -designated place, then offering equivalent access to copy the source code from -the same place satisfies the requirement to distribute the source code, even -though third parties are not compelled to copy the source along with the object -code. - -5. A program that contains no derivative of any portion of the Library, but is -designed to work with the Library by being compiled or linked with it, is called -a "work that uses the Library". Such a work, in isolation, is not a derivative -work of the Library, and therefore falls outside the scope of this License. - -However, linking a "work that uses the Library" with the Library creates an -executable that is a derivative of the Library (because it contains portions of -the Library), rather than a "work that uses the library". The executable is -therefore covered by this License. Section 6 states terms for distribution of -such executables. - -When a "work that uses the Library" uses material from a header file that is -part of the Library, the object code for the work may be a derivative work of -the Library even though the source code is not. Whether this is true is -especially significant if the work can be linked without the Library, or if the -work is itself a library. The threshold for this to be true is not precisely -defined by law. - -If such an object file uses only numerical parameters, data structure layouts -and accessors, and small macros and small inline functions (ten lines or less in -length), then the use of the object file is unrestricted, regardless of whether -it is legally a derivative work. (Executables containing this object code plus -portions of the Library will still fall under Section 6.) - -Otherwise, if the work is a derivative of the Library, you may distribute the -object code for the work under the terms of Section 6. Any executables -containing that work also fall under Section 6, whether or not they are linked -directly with the Library itself. - -6. As an exception to the Sections above, you may also combine or link a "work -that uses the Library" with the Library to produce a work containing portions of -the Library, and distribute that work under terms of your choice, provided that -the terms permit modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - -You must give prominent notice with each copy of the work that the Library is -used in it and that the Library and its use are covered by this License. You -must supply a copy of this License. If the work during execution displays -copyright notices, you must include the copyright notice for the Library among -them, as well as a reference directing the user to the copy of this License. -Also, you must do one of these things: - -a) Accompany the work with the complete corresponding machine-readable source -code for the Library including whatever changes were used in the work (which -must be distributed under Sections 1 and 2 above); and, if the work is an -executable linked with the Library, with the complete machine-readable "work -that uses the Library", as object code and/or source code, so that the user can -modify the Library and then relink to produce a modified executable containing -the modified Library. (It is understood that the user who changes the contents -of definitions files in the Library will not necessarily be able to recompile -the application to use the modified definitions.) - -b) Use a suitable shared library mechanism for linking with the Library. A -suitable mechanism is one that (1) uses at run time a copy of the library -already present on the user's computer system, rather than copying library -functions into the executable, and (2) will operate properly with a modified -version of the library, if the user installs one, as long as the modified -version is interface-compatible with the version that the work was made with. - -c) Accompany the work with a written offer, valid for at least three years, to -give the same user the materials specified in Subsection 6a, above, for a charge -no more than the cost of performing this distribution. - -d) If distribution of the work is made by offering access to copy from a -designated place, offer equivalent access to copy the above specified materials -from the same place. - -e) Verify that the user has already received a copy of these materials or that -you have already sent this user a copy. - -For an executable, the required form of the "work that uses the Library" must -include any data and utility programs needed for reproducing the executable from -it. However, as a special exception, the materials to be distributed need not -include anything that is normally distributed (in either source or binary form) -with the major components (compiler, kernel, and so on) of the operating system -on which the executable runs, unless that component itself accompanies the -executable. - -It may happen that this requirement contradicts the license restrictions of -other proprietary libraries that do not normally accompany the operating system. -Such a contradiction means you cannot use both them and the Library together in -an executable that you distribute. - -7. You may place library facilities that are a work based on the Library -side-by-side in a single library together with other library facilities not -covered by this License, and distribute such a combined library, provided that -the separate distribution of the work based on the Library and of the other -library facilities is otherwise permitted, and provided that you do these two -things: - -a) Accompany the combined library with a copy of the same work based on the -Library, uncombined with any other library facilities. This must be distributed -under the terms of the Sections above. - -b) Give prominent notice with the combined library of the fact that part of it -is a work based on the Library, and explaining where to find the accompanying -uncombined form of the same work. - -8. You may not copy, modify, sublicense, link with, or distribute the Library -except as expressly provided under this License. Any attempt otherwise to copy, -modify, sublicense, link with, or distribute the Library is void, and will -automatically terminate your rights under this License. However, parties who -have received copies, or rights, from you under this License will not have their -licenses terminated so long as such parties remain in full compliance. - -9. You are not required to accept this License, since you have not signed it. -However, nothing else grants you permission to modify or distribute the Library -or its derivative works. These actions are prohibited by law if you do not -accept this License. Therefore, by modifying or distributing the Library (or -any work based on the Library), you indicate your acceptance of this License to -do so, and all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - -10. Each time you redistribute the Library (or any work based on the Library), -the recipient automatically receives a license from the original licensor to -copy, distribute, link with or modify the Library subject to these terms and -conditions. You may not impose any further restrictions on the recipients' -exercise of the rights granted herein. You are not responsible for enforcing -compliance by third parties with this License. - -11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), conditions -are imposed on you (whether by court order, agreement or otherwise) that -contradict the conditions of this License, they do not excuse you from the -conditions of this License. If you cannot distribute so as to satisfy -simultaneously your obligations under this License and any other pertinent -obligations, then as a consequence you may not distribute the Library at all. -For example, if a patent license would not permit royalty-free redistribution of -the Library by all those who receive copies directly or indirectly through you, -then the only way you could satisfy both it and this License would be to refrain -entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, and -the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any patents or -other property right claims or to contest validity of any such claims; this -section has the sole purpose of protecting the integrity of the free software -distribution system which is implemented by public license practices. Many -people have made generous contributions to the wide range of software -distributed through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing to -distribute software through any other system and a licensee cannot impose that -choice. - -This section is intended to make thoroughly clear what is believed to be a -consequence of the rest of this License. - -12. If the distribution and/or use of the Library is restricted in certain -countries either by patents or by copyrighted interfaces, the original copyright -holder who places the Library under this License may add an explicit -geographical distribution limitation excluding those countries, so that -distribution is permitted only in or among countries not thus excluded. In such -case, this License incorporates the limitation as if written in the body of this -License. - -13. The Free Software Foundation may publish revised and/or new versions of the -Lesser General Public License from time to time. Such new versions will be -similar in spirit to the present version, but may differ in detail to address -new problems or concerns. - -Each version is given a distinguishing version number. If the Library specifies -a version number of this License which applies to it and "any later version", -you have the option of following the terms and conditions either of that version -or of any later version published by the Free Software Foundation. If the -Library does not specify a license version number, you may choose any version -ever published by the Free Software Foundation. - -14. If you wish to incorporate parts of the Library into other free programs -whose distribution conditions are incompatible with these, write to the author -to ask for permission. For software which is copyrighted by the Free Software -Foundation, write to the Free Software Foundation; we sometimes make exceptions -for this. Our decision will be guided by the two goals of preserving the free -status of all derivatives of our free software and of promoting the sharing and -reuse of software generally. - -NO WARRANTY - -15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR -THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE -STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY -"AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, -BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A -PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - -16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL -ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE -LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, -SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY -TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF -THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER -PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND -CONDITIONS - -How to Apply These Terms to Your New Libraries - -If you develop a new library, and you want it to be of the greatest possible use -to the public, we recommend making it free software that everyone can -redistribute and change. You can do so by permitting redistribution under these -terms (or, alternatively, under the terms of the ordinary General Public -License). - -To apply these terms, attach the following notices to the library. It is safest -to attach them to the start of each source file to most effectively convey the -exclusion of warranty; and each file should have at least the "copyright" line -and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, write to the Free Software - Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your school, -if any, to sign a "copyright disclaimer" for the library, if necessary. Here is -a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in - the library `Frob' (a library for tweaking knobs) written - by James Random Hacker. - - signature of Ty Coon, 1 April 1990 - - Ty Coon, President of Vice - -That's all there is to it! - - - unRAR restriction - ----------------- - - The unRAR sources cannot be used to re-create the RAR compression -algorithm, - which is proprietary. Distribution of modified unRAR sources in separate -form - or as a part of other software is permitted, provided that it is clearly - stated in the documentation and source comments that the code may - not be used to develop a RAR (WinRAR) compatible archiver. - - - AES code license - ---------------- - - Copyright (c) 2001, Dr Brian Gladman - - LICENSE TERMS - - The free distribution and use of this software in both source and binary - form is allowed (with or without changes) provided that: - - 1. distributions of this source code include the above copyright - notice, this list of conditions and the following disclaimer; - - 2. distributions in binary form include the above copyright - notice, this list of conditions and the following disclaimer - in the documentation and/or other associated materials; - - 3. the copyright holder's name is not used to endorse products - built using this software without specific written permission. - - DISCLAIMER - - This software is provided 'as is' with no explicit or implied warranties - in respect of its properties, including, but not limited to, correctness - and fitness for purpose. - - -*************************************************************************** - -%%The following software may be included in this product: -UPX - -Use of any of this software is governed by the terms of the license below: - ------BEGIN PGP SIGNED MESSAGE----- - - - ooooo ooo ooooooooo. ooooooo ooooo - `888' `8' `888 `Y88. `8888 d8' - 888 8 888 .d88' Y888..8P - 888 8 888ooo88P' `8888' - 888 8 888 .8PY888. - `88. .8' 888 d8' `888b - `YbodP' o888o o888o o88888o - - - The Ultimate Packer for eXecutables - Copyright (c) 1996-2000 Markus Oberhumer & Laszlo Molnar - http://wildsau.idv.uni-linz.ac.at/mfx/upx.html - http://www.nexus.hu/upx - http://upx.tsx.org - - -PLEASE CAREFULLY READ THIS LICENSE AGREEMENT, ESPECIALLY IF YOU PLAN -TO MODIFY THE UPX SOURCE CODE OR USE A MODIFIED UPX VERSION. - - -ABSTRACT -======== - - UPX and UCL are copyrighted software distributed under the terms - of the GNU General Public License (hereinafter the "GPL"). - - The stub which is imbedded in each UPX compressed program is part - of UPX and UCL, and contains code that is under our copyright. The - terms of the GNU General Public License still apply as compressing - a program is a special form of linking with our stub. - - As a special exception we grant the free usage of UPX for all - executables, including commercial programs. - See below for details and restrictions. - - -COPYRIGHT -========= - - UPX and UCL are copyrighted software. All rights remain with the authors. - - UPX is Copyright (C) 1996-2000 Markus Franz Xaver Johannes Oberhumer - UPX is Copyright (C) 1996-2000 Laszlo Molnar - - UCL is Copyright (C) 1996-2000 Markus Franz Xaver Johannes Oberhumer - - -GNU GENERAL PUBLIC LICENSE -========================== - - UPX and the UCL library are free software; you can redistribute them - and/or modify them under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - UPX and UCL are distributed in the hope that they will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; see the file COPYING. - - -SPECIAL EXCEPTION FOR COMPRESSED EXECUTABLES -============================================ - - The stub which is imbedded in each UPX compressed program is part - of UPX and UCL, and contains code that is under our copyright. The - terms of the GNU General Public License still apply as compressing - a program is a special form of linking with our stub. - - Hereby Markus F.X.J. Oberhumer and Laszlo Molnar grant you special - permission to freely use and distribute all UPX compressed programs - (including commercial ones), subject to the following restrictions: - - 1. You must compress your program with a completely unmodified UPX - version; either with our precompiled version, or (at your option) - with a self compiled version of the unmodified UPX sources as - distributed by us. - 2. This also implies that the UPX stub must be completely unmodfied, i.e. - the stub imbedded in your compressed program must be byte-identical - to the stub that is produced by the official unmodified UPX version. - 3. The decompressor and any other code from the stub must exclusively get - used by the unmodified UPX stub for decompressing your program at - program startup. No portion of the stub may get read, copied, - called or otherwise get used or accessed by your program. - - -ANNOTATIONS -=========== - - - You can use a modified UPX version or modified UPX stub only for - programs that are compatible with the GNU General Public License. - - - We grant you special permission to freely use and distribute all UPX - compressed programs. But any modification of the UPX stub (such as, - but not limited to, removing our copyright string or making your - program non-decompressible) will immediately revoke your right to - use and distribute a UPX compressed program. - - - UPX is not a software protection tool; by requiring that you use - the unmodified UPX version for your proprietary programs we - make sure that any user can decompress your program. This protects - both you and your users as nobody can hide malicious code - - any program that cannot be decompressed is highly suspicious - by definition. - - - You can integrate all or part of UPX and UCL into projects that - are compatible with the GNU GPL, but obviously you cannot grant - any special exceptions beyond the GPL for our code in your project. - - - We want to actively support manufacturers of virus scanners and - similar security software. Please contact us if you would like to - incorporate parts of UPX or UCL into such a product. - - - -Markus F.X.J. Oberhumer Laszlo Molnar -markus.oberhumer@jk.uni-linz.ac.at ml1050@cdata.tvnet.hu - -Linz, Austria, 25 Feb 2000 - -Additional License(s) - -The UPX license file is at http://upx.sourceforge.net/upx-license.html. - -*************************************************************************** - -%%The following software may be included in this product: -LZMA Software Development Kit - -Use of any of this software is governed by the terms of the license below: - -License - -LZMA SDK is available under any of the following licenses: - - 1. GNU Lesser General Public License (GNU LGPL) - 2. Common Public License (CPL) - 3. Simplified license for unmodified code (read SPECIAL EXCEPTION) - 4. Proprietary license - -This means that you can select one of these four options and follow rules of -that license. - -SPECIAL EXCEPTION: Igor Pavlov, as the author of this code, expressly permit -you statically or dynamically to link your code (or bind by name) to the files -from LZMA SDK without subjecting your linked code to the terms of the CPL or GNU -LGPL. Any modification or addition to any file in the LZMA SDK, however, is -subject to the GNU LGPL or CPL terms. - -This SPECIAL EXCEPTION allows you to use LZMA SDK in applications with -proprietary code, provided you keep the LZMA SDK code unmodified. - -SPECIAL EXCEPTION #2: Igor Pavlov, as the author of this code, expressly -permits you to use LZMA SDK 4.43 under the same terms and conditions contained -in the License Agreement you have for any previous version of LZMA SDK developed -by Igor Pavlov. - -SPECIAL EXCEPTION #2 allows holders of proprietary licenses to use latest -version of LZMA SDK as update for previous versions. - -GNU LGPL and CPL are pretty similar and both these licenses are classified as -free software licenses at http://www.gnu.org/ and OSI-approved at -http://www.opensource.org/. - -LZMA SDK also is available under a proprietary license which can include: - -1. The right to modify code from the LZMA SDK without subjecting the modified -code to the terms of the CPL or GNU LGPL - -2. Technical support for LZMA SDK via email - -To request such a proprietary license, or for any additional consultations, send -an email message, using the 7-Zip support page: Send message to LZMA developer - -The source code of 7-Zip is released under the terms of the GNU LGPL. You can -download the source code of 7-Zip at 7-Zip's Source Forge Page - -Additional License(s) - -The license included with the software differs slightly from the version posted -on the website. Specifically it includes SPECIAL EXCEPTION #3, which is not -present in the license on the website. The license from the software archive -follows: - -LICENSE -------- - -LZMA SDK is available under any of the following licenses: - -1) GNU Lesser General Public License (GNU LGPL) -2) Common Public License (CPL) -3) Simplified license for unmodified code (read SPECIAL EXCEPTION) -4) Proprietary license - -It means that you can select one of these four options and follow rules of that license. - - -1,2) GNU LGPL and CPL licenses are pretty similar and both these licenses are -classified as - - "Free software licenses" at http://www.gnu.org/ - - "OSI-approved" at http://www.opensource.org/ - - -3) SPECIAL EXCEPTION - -Igor Pavlov, as the author of this code, expressly permits you to statically or -dynamically link your code (or bind by name) to the files from LZMA SDK without -subjecting your linked code to the terms of the CPL or GNU LGPL. Any -modifications or additions to files from LZMA SDK, however, are subject to the -GNU LGPL or CPL terms. - -SPECIAL EXCEPTION allows you to use LZMA SDK in applications with closed code, -while you keep LZMA SDK code unmodified. - - -SPECIAL EXCEPTION #2: Igor Pavlov, as the author of this code, expressly -permits you to use this code under the same terms and conditions contained in -the License Agreement you have for any previous version of LZMA SDK developed by -Igor Pavlov. - -SPECIAL EXCEPTION #2 allows owners of proprietary licenses to use latest version -of LZMA SDK as update for previous versions. - - -SPECIAL EXCEPTION #3: Igor Pavlov, as the author of this code, expressly -permits you to use code of the following files: BranchTypes.h, LzmaTypes.h, -LzmaTest.c, LzmaStateTest.c, LzmaAlone.cpp, LzmaAlone.cs, LzmaAlone.java as -public domain code. - - -4) Proprietary license - -LZMA SDK also can be available under a proprietary license which -can include: - -1) Right to modify code without subjecting modified code to the terms of the CPL or GNU LGPL -2) Technical support for code - -To request such proprietary license or any additional consultations, send email -message from that page:http://www.7-zip.org/support.html - - -You should have received a copy of the GNU Lesser General Public License along -with this library; if not, write to the Free Software Foundation, Inc., 59 -Temple Place, Suite 330, Boston, MA 02111-1307 USA - -You should have received a copy of the Common Public License along with this -library. diff --git a/windows-dependencies/Java/Welcome.html b/windows-dependencies/Java/Welcome.html deleted file mode 100644 index 33a0ba544..000000000 --- a/windows-dependencies/Java/Welcome.html +++ /dev/null @@ -1,26 +0,0 @@ - - - -Welcome to the Java(TM) Platform - - - - -

    Welcome to the JavaTM Platform

    -

    Welcome to the JavaTM Standard Edition Runtime - Environment. This provides complete runtime support for Java applications. -

    The runtime environment includes the JavaTM - Plug-in product which supports the Java environment inside web browsers. -

    References

    -

    -See the Java Plug-in product -documentation for more information on using the Java Plug-in product. -

    See the Java Platform web site for - more information on the Java Platform. -


    - -Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. - -

    - - diff --git a/windows-dependencies/Java/bin/JdbcOdbc.dll b/windows-dependencies/Java/bin/JdbcOdbc.dll deleted file mode 100644 index f478d2f2a..000000000 Binary files a/windows-dependencies/Java/bin/JdbcOdbc.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/awt.dll b/windows-dependencies/Java/bin/awt.dll deleted file mode 100644 index 230e308e8..000000000 Binary files a/windows-dependencies/Java/bin/awt.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/axbridge.dll b/windows-dependencies/Java/bin/axbridge.dll deleted file mode 100644 index e4e261db3..000000000 Binary files a/windows-dependencies/Java/bin/axbridge.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/client/Xusage.txt b/windows-dependencies/Java/bin/client/Xusage.txt deleted file mode 100644 index 11302aaa6..000000000 --- a/windows-dependencies/Java/bin/client/Xusage.txt +++ /dev/null @@ -1,24 +0,0 @@ - -Xmixed mixed mode execution (default) - -Xint interpreted mode execution only - -Xbootclasspath: - set search path for bootstrap classes and resources - -Xbootclasspath/a: - append to end of bootstrap class path - -Xbootclasspath/p: - prepend in front of bootstrap class path - -Xnoclassgc disable class garbage collection - -Xincgc enable incremental garbage collection - -Xloggc: log GC status to a file with time stamps - -Xbatch disable background compilation - -Xms set initial Java heap size - -Xmx set maximum Java heap size - -Xss set java thread stack size - -Xprof output cpu profiling data - -Xfuture enable strictest checks, anticipating future default - -Xrs reduce use of OS signals by Java/VM (see documentation) - -Xcheck:jni perform additional checks for JNI functions - -Xshare:off do not attempt to use shared class data - -Xshare:auto use shared class data if possible (default) - -Xshare:on require using shared class data, otherwise fail. - -The -X options are non-standard and subject to change without notice. diff --git a/windows-dependencies/Java/bin/client/classes.jsa b/windows-dependencies/Java/bin/client/classes.jsa deleted file mode 100644 index d88d9329e..000000000 Binary files a/windows-dependencies/Java/bin/client/classes.jsa and /dev/null differ diff --git a/windows-dependencies/Java/bin/client/jvm.dll b/windows-dependencies/Java/bin/client/jvm.dll deleted file mode 100644 index fed2550fa..000000000 Binary files a/windows-dependencies/Java/bin/client/jvm.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/cmm.dll b/windows-dependencies/Java/bin/cmm.dll deleted file mode 100644 index 2a0af0601..000000000 Binary files a/windows-dependencies/Java/bin/cmm.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/dcpr.dll b/windows-dependencies/Java/bin/dcpr.dll deleted file mode 100644 index 08047a85a..000000000 Binary files a/windows-dependencies/Java/bin/dcpr.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/deploy.dll b/windows-dependencies/Java/bin/deploy.dll deleted file mode 100644 index 82143f210..000000000 Binary files a/windows-dependencies/Java/bin/deploy.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/deployJava1.dll b/windows-dependencies/Java/bin/deployJava1.dll deleted file mode 100644 index d499c380c..000000000 Binary files a/windows-dependencies/Java/bin/deployJava1.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/dt_shmem.dll b/windows-dependencies/Java/bin/dt_shmem.dll deleted file mode 100644 index bb25cb4fd..000000000 Binary files a/windows-dependencies/Java/bin/dt_shmem.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/dt_socket.dll b/windows-dependencies/Java/bin/dt_socket.dll deleted file mode 100644 index 52c3cc6f4..000000000 Binary files a/windows-dependencies/Java/bin/dt_socket.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/eula.dll b/windows-dependencies/Java/bin/eula.dll deleted file mode 100644 index 6fc729dca..000000000 Binary files a/windows-dependencies/Java/bin/eula.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/fontmanager.dll b/windows-dependencies/Java/bin/fontmanager.dll deleted file mode 100644 index 4f34c8294..000000000 Binary files a/windows-dependencies/Java/bin/fontmanager.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/hpi.dll b/windows-dependencies/Java/bin/hpi.dll deleted file mode 100644 index 994f8d590..000000000 Binary files a/windows-dependencies/Java/bin/hpi.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/hprof.dll b/windows-dependencies/Java/bin/hprof.dll deleted file mode 100644 index 02b0bebbb..000000000 Binary files a/windows-dependencies/Java/bin/hprof.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/instrument.dll b/windows-dependencies/Java/bin/instrument.dll deleted file mode 100644 index abce60dd3..000000000 Binary files a/windows-dependencies/Java/bin/instrument.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/ioser12.dll b/windows-dependencies/Java/bin/ioser12.dll deleted file mode 100644 index 039f57c9a..000000000 Binary files a/windows-dependencies/Java/bin/ioser12.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/j2pcsc.dll b/windows-dependencies/Java/bin/j2pcsc.dll deleted file mode 100644 index b164a970a..000000000 Binary files a/windows-dependencies/Java/bin/j2pcsc.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/j2pkcs11.dll b/windows-dependencies/Java/bin/j2pkcs11.dll deleted file mode 100644 index 7338c1456..000000000 Binary files a/windows-dependencies/Java/bin/j2pkcs11.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jaas_nt.dll b/windows-dependencies/Java/bin/jaas_nt.dll deleted file mode 100644 index 3abbda668..000000000 Binary files a/windows-dependencies/Java/bin/jaas_nt.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/java-rmi.exe b/windows-dependencies/Java/bin/java-rmi.exe deleted file mode 100644 index 810cbfafa..000000000 Binary files a/windows-dependencies/Java/bin/java-rmi.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/java.dll b/windows-dependencies/Java/bin/java.dll deleted file mode 100644 index d65105dbf..000000000 Binary files a/windows-dependencies/Java/bin/java.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/java.exe b/windows-dependencies/Java/bin/java.exe deleted file mode 100644 index c666fcfad..000000000 Binary files a/windows-dependencies/Java/bin/java.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/java_crw_demo.dll b/windows-dependencies/Java/bin/java_crw_demo.dll deleted file mode 100644 index 487875c35..000000000 Binary files a/windows-dependencies/Java/bin/java_crw_demo.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/javacpl.cpl b/windows-dependencies/Java/bin/javacpl.cpl deleted file mode 100644 index 07dd2d28e..000000000 Binary files a/windows-dependencies/Java/bin/javacpl.cpl and /dev/null differ diff --git a/windows-dependencies/Java/bin/javacpl.exe b/windows-dependencies/Java/bin/javacpl.exe deleted file mode 100644 index 9ad015664..000000000 Binary files a/windows-dependencies/Java/bin/javacpl.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/javaw.exe b/windows-dependencies/Java/bin/javaw.exe deleted file mode 100644 index d3a347b7b..000000000 Binary files a/windows-dependencies/Java/bin/javaw.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/javaws.exe b/windows-dependencies/Java/bin/javaws.exe deleted file mode 100644 index 15e98f17d..000000000 Binary files a/windows-dependencies/Java/bin/javaws.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/jawt.dll b/windows-dependencies/Java/bin/jawt.dll deleted file mode 100644 index b75e3cef4..000000000 Binary files a/windows-dependencies/Java/bin/jawt.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jbroker.exe b/windows-dependencies/Java/bin/jbroker.exe deleted file mode 100644 index aea660eaa..000000000 Binary files a/windows-dependencies/Java/bin/jbroker.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/jdwp.dll b/windows-dependencies/Java/bin/jdwp.dll deleted file mode 100644 index fbab76dec..000000000 Binary files a/windows-dependencies/Java/bin/jdwp.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jkernel.dll b/windows-dependencies/Java/bin/jkernel.dll deleted file mode 100644 index 600028238..000000000 Binary files a/windows-dependencies/Java/bin/jkernel.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jli.dll b/windows-dependencies/Java/bin/jli.dll deleted file mode 100644 index a7b0442cc..000000000 Binary files a/windows-dependencies/Java/bin/jli.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jp2iexp.dll b/windows-dependencies/Java/bin/jp2iexp.dll deleted file mode 100644 index a2db83912..000000000 Binary files a/windows-dependencies/Java/bin/jp2iexp.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jp2launcher.exe b/windows-dependencies/Java/bin/jp2launcher.exe deleted file mode 100644 index 8562cdc93..000000000 Binary files a/windows-dependencies/Java/bin/jp2launcher.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/jp2native.dll b/windows-dependencies/Java/bin/jp2native.dll deleted file mode 100644 index af6856698..000000000 Binary files a/windows-dependencies/Java/bin/jp2native.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jp2ssv.dll b/windows-dependencies/Java/bin/jp2ssv.dll deleted file mode 100644 index 37a5d2cde..000000000 Binary files a/windows-dependencies/Java/bin/jp2ssv.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jpeg.dll b/windows-dependencies/Java/bin/jpeg.dll deleted file mode 100644 index 190ce8b84..000000000 Binary files a/windows-dependencies/Java/bin/jpeg.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jpicom.dll b/windows-dependencies/Java/bin/jpicom.dll deleted file mode 100644 index 81fb47bba..000000000 Binary files a/windows-dependencies/Java/bin/jpicom.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jpiexp.dll b/windows-dependencies/Java/bin/jpiexp.dll deleted file mode 100644 index b4f384ac8..000000000 Binary files a/windows-dependencies/Java/bin/jpiexp.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jpinscp.dll b/windows-dependencies/Java/bin/jpinscp.dll deleted file mode 100644 index 93e5f34e4..000000000 Binary files a/windows-dependencies/Java/bin/jpinscp.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jpioji.dll b/windows-dependencies/Java/bin/jpioji.dll deleted file mode 100644 index 499ea68df..000000000 Binary files a/windows-dependencies/Java/bin/jpioji.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jpishare.dll b/windows-dependencies/Java/bin/jpishare.dll deleted file mode 100644 index 21b10c3a4..000000000 Binary files a/windows-dependencies/Java/bin/jpishare.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jqs.exe b/windows-dependencies/Java/bin/jqs.exe deleted file mode 100644 index 0145dbb8c..000000000 Binary files a/windows-dependencies/Java/bin/jqs.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/jqsnotify.exe b/windows-dependencies/Java/bin/jqsnotify.exe deleted file mode 100644 index d0120ef88..000000000 Binary files a/windows-dependencies/Java/bin/jqsnotify.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/jsound.dll b/windows-dependencies/Java/bin/jsound.dll deleted file mode 100644 index fd02b7b2d..000000000 Binary files a/windows-dependencies/Java/bin/jsound.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/jsoundds.dll b/windows-dependencies/Java/bin/jsoundds.dll deleted file mode 100644 index 6a3017637..000000000 Binary files a/windows-dependencies/Java/bin/jsoundds.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/keytool.exe b/windows-dependencies/Java/bin/keytool.exe deleted file mode 100644 index 516b7dbb4..000000000 Binary files a/windows-dependencies/Java/bin/keytool.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/kinit.exe b/windows-dependencies/Java/bin/kinit.exe deleted file mode 100644 index cea9259fd..000000000 Binary files a/windows-dependencies/Java/bin/kinit.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/klist.exe b/windows-dependencies/Java/bin/klist.exe deleted file mode 100644 index c3f3ce10b..000000000 Binary files a/windows-dependencies/Java/bin/klist.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/ktab.exe b/windows-dependencies/Java/bin/ktab.exe deleted file mode 100644 index 2aca306f5..000000000 Binary files a/windows-dependencies/Java/bin/ktab.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/management.dll b/windows-dependencies/Java/bin/management.dll deleted file mode 100644 index 35b3cf02a..000000000 Binary files a/windows-dependencies/Java/bin/management.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/mlib_image.dll b/windows-dependencies/Java/bin/mlib_image.dll deleted file mode 100644 index 7c36659f5..000000000 Binary files a/windows-dependencies/Java/bin/mlib_image.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/msvcr71.dll b/windows-dependencies/Java/bin/msvcr71.dll deleted file mode 100644 index 9d9e0286c..000000000 Binary files a/windows-dependencies/Java/bin/msvcr71.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/msvcrt.dll b/windows-dependencies/Java/bin/msvcrt.dll deleted file mode 100644 index 16bb3401c..000000000 Binary files a/windows-dependencies/Java/bin/msvcrt.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/net.dll b/windows-dependencies/Java/bin/net.dll deleted file mode 100644 index cb9c7a04b..000000000 Binary files a/windows-dependencies/Java/bin/net.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/new_plugin/msvcr71.dll b/windows-dependencies/Java/bin/new_plugin/msvcr71.dll deleted file mode 100644 index 9d9e0286c..000000000 Binary files a/windows-dependencies/Java/bin/new_plugin/msvcr71.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/new_plugin/npdeployJava1.dll b/windows-dependencies/Java/bin/new_plugin/npdeployJava1.dll deleted file mode 100644 index 76b4eb13a..000000000 Binary files a/windows-dependencies/Java/bin/new_plugin/npdeployJava1.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/new_plugin/npjp2.dll b/windows-dependencies/Java/bin/new_plugin/npjp2.dll deleted file mode 100644 index ad80010e7..000000000 Binary files a/windows-dependencies/Java/bin/new_plugin/npjp2.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/nio.dll b/windows-dependencies/Java/bin/nio.dll deleted file mode 100644 index 82c4ace48..000000000 Binary files a/windows-dependencies/Java/bin/nio.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/npdeployJava1.dll b/windows-dependencies/Java/bin/npdeployJava1.dll deleted file mode 100644 index 76b4eb13a..000000000 Binary files a/windows-dependencies/Java/bin/npdeployJava1.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/npjpi160_30.dll b/windows-dependencies/Java/bin/npjpi160_30.dll deleted file mode 100644 index 9e643d255..000000000 Binary files a/windows-dependencies/Java/bin/npjpi160_30.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/npoji610.dll b/windows-dependencies/Java/bin/npoji610.dll deleted file mode 100644 index 99e7aee58..000000000 Binary files a/windows-dependencies/Java/bin/npoji610.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/npt.dll b/windows-dependencies/Java/bin/npt.dll deleted file mode 100644 index d942ca005..000000000 Binary files a/windows-dependencies/Java/bin/npt.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/orbd.exe b/windows-dependencies/Java/bin/orbd.exe deleted file mode 100644 index d5d2c6e90..000000000 Binary files a/windows-dependencies/Java/bin/orbd.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/pack200.exe b/windows-dependencies/Java/bin/pack200.exe deleted file mode 100644 index 602168f9c..000000000 Binary files a/windows-dependencies/Java/bin/pack200.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/policytool.exe b/windows-dependencies/Java/bin/policytool.exe deleted file mode 100644 index a00949f64..000000000 Binary files a/windows-dependencies/Java/bin/policytool.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/regutils.dll b/windows-dependencies/Java/bin/regutils.dll deleted file mode 100644 index 618d27f0a..000000000 Binary files a/windows-dependencies/Java/bin/regutils.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/rmi.dll b/windows-dependencies/Java/bin/rmi.dll deleted file mode 100644 index ab879636c..000000000 Binary files a/windows-dependencies/Java/bin/rmi.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/rmid.exe b/windows-dependencies/Java/bin/rmid.exe deleted file mode 100644 index a4fd8e6c1..000000000 Binary files a/windows-dependencies/Java/bin/rmid.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/rmiregistry.exe b/windows-dependencies/Java/bin/rmiregistry.exe deleted file mode 100644 index 319ecbe82..000000000 Binary files a/windows-dependencies/Java/bin/rmiregistry.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/servertool.exe b/windows-dependencies/Java/bin/servertool.exe deleted file mode 100644 index d71567941..000000000 Binary files a/windows-dependencies/Java/bin/servertool.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/splashscreen.dll b/windows-dependencies/Java/bin/splashscreen.dll deleted file mode 100644 index e2c1b33e0..000000000 Binary files a/windows-dependencies/Java/bin/splashscreen.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/ssv.dll b/windows-dependencies/Java/bin/ssv.dll deleted file mode 100644 index b7b473cee..000000000 Binary files a/windows-dependencies/Java/bin/ssv.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/ssvagent.exe b/windows-dependencies/Java/bin/ssvagent.exe deleted file mode 100644 index 774e71cb2..000000000 Binary files a/windows-dependencies/Java/bin/ssvagent.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/sunmscapi.dll b/windows-dependencies/Java/bin/sunmscapi.dll deleted file mode 100644 index 693562168..000000000 Binary files a/windows-dependencies/Java/bin/sunmscapi.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/tnameserv.exe b/windows-dependencies/Java/bin/tnameserv.exe deleted file mode 100644 index 5db63ca51..000000000 Binary files a/windows-dependencies/Java/bin/tnameserv.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/unicows.dll b/windows-dependencies/Java/bin/unicows.dll deleted file mode 100644 index 7f5aea76b..000000000 Binary files a/windows-dependencies/Java/bin/unicows.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/unpack.dll b/windows-dependencies/Java/bin/unpack.dll deleted file mode 100644 index 8628ac77f..000000000 Binary files a/windows-dependencies/Java/bin/unpack.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/unpack200.exe b/windows-dependencies/Java/bin/unpack200.exe deleted file mode 100644 index 58495ad11..000000000 Binary files a/windows-dependencies/Java/bin/unpack200.exe and /dev/null differ diff --git a/windows-dependencies/Java/bin/verify.dll b/windows-dependencies/Java/bin/verify.dll deleted file mode 100644 index d4ba81df7..000000000 Binary files a/windows-dependencies/Java/bin/verify.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/w2k_lsa_auth.dll b/windows-dependencies/Java/bin/w2k_lsa_auth.dll deleted file mode 100644 index 06bbe2e04..000000000 Binary files a/windows-dependencies/Java/bin/w2k_lsa_auth.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/wsdetect.dll b/windows-dependencies/Java/bin/wsdetect.dll deleted file mode 100644 index 8bb90c0e8..000000000 Binary files a/windows-dependencies/Java/bin/wsdetect.dll and /dev/null differ diff --git a/windows-dependencies/Java/bin/zip.dll b/windows-dependencies/Java/bin/zip.dll deleted file mode 100644 index 63deea580..000000000 Binary files a/windows-dependencies/Java/bin/zip.dll and /dev/null differ diff --git a/windows-dependencies/Java/lib/audio/soundbank.gm b/windows-dependencies/Java/lib/audio/soundbank.gm deleted file mode 100644 index 83c2f878d..000000000 Binary files a/windows-dependencies/Java/lib/audio/soundbank.gm and /dev/null differ diff --git a/windows-dependencies/Java/lib/calendars.properties b/windows-dependencies/Java/lib/calendars.properties deleted file mode 100644 index ebf0b6762..000000000 --- a/windows-dependencies/Java/lib/calendars.properties +++ /dev/null @@ -1,37 +0,0 @@ -# -# %W% %E% -# -# Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved. -# ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. -# - -# -# Japanese imperial calendar -# -# Meiji since 1868-01-01 00:00:00 local time (Gregorian) -# Taisho since 1912-07-30 00:00:00 local time (Gregorian) -# Showa since 1926-12-25 00:00:00 local time (Gregorian) -# Heisei since 1989-01-08 00:00:00 local time (Gregorian) -calendar.japanese.type: LocalGregorianCalendar -calendar.japanese.eras: \ - name=Meiji,abbr=M,since=-3218832000000; \ - name=Taisho,abbr=T,since=-1812153600000; \ - name=Showa,abbr=S,since=-1357603200000; \ - name=Heisei,abbr=H,since=600220800000 - -# -# Taiwanese calendar -# Minguo since 1911-01-01 00:00:00 local time (Gregorian) -calendar.taiwanese.type: LocalGregorianCalendar -calendar.taiwanese.eras: \ - name=MinGuo,since=-1830384000000 - -# -# Thai Buddhist calendar -# Buddhist Era since -542-01-01 00:00:00 local time (Gregorian) -calendar.thai-buddhist.type: LocalGregorianCalendar -calendar.thai-buddhist.eras: \ - name=BuddhistEra,abbr=B.E.,since=-79302585600000 -calendar.thai-buddhist.year-boundary: \ - day1=4-1,since=-79302585600000; \ - day1=1-1,since=-915148800000 diff --git a/windows-dependencies/Java/lib/charsets.jar b/windows-dependencies/Java/lib/charsets.jar deleted file mode 100644 index d4a51c871..000000000 Binary files a/windows-dependencies/Java/lib/charsets.jar and /dev/null differ diff --git a/windows-dependencies/Java/lib/classlist b/windows-dependencies/Java/lib/classlist deleted file mode 100644 index d70d0c98c..000000000 --- a/windows-dependencies/Java/lib/classlist +++ /dev/null @@ -1,2395 +0,0 @@ -java/lang/Object -java/lang/String -java/io/Serializable -java/lang/Comparable -java/lang/CharSequence -java/lang/Class -java/lang/reflect/GenericDeclaration -java/lang/reflect/Type -java/lang/reflect/AnnotatedElement -java/lang/Cloneable -java/lang/ClassLoader -java/lang/System -java/lang/Throwable -java/lang/Error -java/lang/ThreadDeath -java/lang/Exception -java/lang/RuntimeException -java/security/ProtectionDomain -java/security/AccessControlContext -java/lang/ClassNotFoundException -java/lang/NoClassDefFoundError -java/lang/LinkageError -java/lang/ClassCastException -java/lang/ArrayStoreException -java/lang/VirtualMachineError -java/lang/OutOfMemoryError -java/lang/StackOverflowError -java/lang/IllegalMonitorStateException -java/lang/ref/Reference -java/lang/ref/SoftReference -java/lang/ref/WeakReference -java/lang/ref/FinalReference -java/lang/ref/PhantomReference -java/lang/ref/Finalizer -java/lang/Thread -java/lang/Runnable -java/lang/ThreadGroup -java/lang/Thread$UncaughtExceptionHandler -java/util/Properties -java/util/Hashtable -java/util/Map -java/util/Dictionary -java/lang/reflect/AccessibleObject -java/lang/reflect/Field -java/lang/reflect/Member -java/lang/reflect/Method -java/lang/reflect/Constructor -sun/reflect/MagicAccessorImpl -sun/reflect/MethodAccessorImpl -sun/reflect/MethodAccessor -sun/reflect/ConstructorAccessorImpl -sun/reflect/ConstructorAccessor -sun/reflect/DelegatingClassLoader -sun/reflect/ConstantPool -sun/reflect/UnsafeStaticFieldAccessorImpl -sun/reflect/UnsafeFieldAccessorImpl -sun/reflect/FieldAccessorImpl -sun/reflect/FieldAccessor -java/util/Vector -java/util/List -java/util/Collection -java/lang/Iterable -java/util/RandomAccess -java/util/AbstractList -java/util/AbstractCollection -java/lang/StringBuffer -java/lang/AbstractStringBuilder -java/lang/Appendable -java/lang/StackTraceElement -java/nio/Buffer -sun/misc/AtomicLongCSImpl -sun/misc/AtomicLong -java/lang/Boolean -java/lang/Character -java/lang/Float -java/lang/Number -java/lang/Double -java/lang/Byte -java/lang/Short -java/lang/Integer -java/lang/Long -java/lang/NullPointerException -java/lang/ArithmeticException -java/io/ObjectStreamField -java/lang/String$CaseInsensitiveComparator -java/util/Comparator -java/lang/RuntimePermission -java/security/BasicPermission -java/security/Permission -java/security/Guard -sun/misc/SoftCache -java/util/AbstractMap -java/lang/ref/ReferenceQueue -java/lang/ref/ReferenceQueue$Null -java/lang/ref/ReferenceQueue$Lock -java/util/HashMap -java/lang/annotation/Annotation -java/util/HashMap$Entry -java/util/Map$Entry -java/security/AccessController -java/lang/reflect/ReflectPermission -sun/reflect/ReflectionFactory$GetReflectionFactoryAction -java/security/PrivilegedAction -java/util/Stack -sun/reflect/ReflectionFactory -java/lang/ref/Reference$Lock -java/lang/ref/Reference$ReferenceHandler -java/lang/ref/Finalizer$FinalizerThread -java/util/Hashtable$EmptyEnumerator -java/util/Enumeration -java/util/Hashtable$EmptyIterator -java/util/Iterator -java/util/Hashtable$Entry -sun/misc/Version -java/lang/Runtime -sun/reflect/Reflection -java/util/Collections -java/util/Collections$EmptySet -java/util/AbstractSet -java/util/Set -java/util/Collections$EmptyList -java/util/Collections$EmptyMap -java/util/Collections$ReverseComparator -java/util/Collections$SynchronizedMap -java/io/File -java/io/FileSystem -java/io/WinNTFileSystem -java/io/Win32FileSystem -java/io/ExpiringCache -java/io/ExpiringCache$1 -java/util/LinkedHashMap -java/util/LinkedHashMap$Entry -sun/security/action/GetPropertyAction -java/lang/StringBuilder -java/util/Arrays -java/lang/Math -java/io/File$1 -sun/misc/JavaIODeleteOnExitAccess -sun/misc/SharedSecrets -sun/misc/Unsafe -java/lang/NoSuchMethodError -java/lang/IncompatibleClassChangeError -sun/jkernel/DownloadManager -sun/jkernel/DownloadManager$1 -java/lang/ThreadLocal -java/util/concurrent/atomic/AtomicInteger -java/lang/Class$3 -java/lang/reflect/Modifier -java/lang/reflect/ReflectAccess -sun/reflect/LangReflectAccess -sun/jkernel/DownloadManager$2 -java/lang/ClassLoader$3 -java/io/ExpiringCache$Entry -java/lang/ClassLoader$NativeLibrary -java/io/FileInputStream -java/io/InputStream -java/io/Closeable -java/io/FileDescriptor -java/io/FileOutputStream -java/io/OutputStream -java/io/Flushable -java/io/BufferedInputStream -java/io/FilterInputStream -java/util/concurrent/atomic/AtomicReferenceFieldUpdater -java/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl -sun/reflect/misc/ReflectUtil -java/io/PrintStream -java/io/FilterOutputStream -java/io/BufferedOutputStream -java/io/OutputStreamWriter -java/io/Writer -sun/nio/cs/StreamEncoder -java/nio/charset/Charset -sun/nio/cs/StandardCharsets -sun/nio/cs/FastCharsetProvider -java/nio/charset/spi/CharsetProvider -sun/nio/cs/StandardCharsets$Aliases -sun/util/PreHashedMap -sun/nio/cs/StandardCharsets$Classes -sun/nio/cs/StandardCharsets$Cache -sun/nio/cs/MS1252 -sun/nio/cs/HistoricallyNamedCharset -java/lang/Class$1 -sun/reflect/ReflectionFactory$1 -sun/reflect/NativeConstructorAccessorImpl -sun/reflect/DelegatingConstructorAccessorImpl -sun/misc/VM -sun/nio/cs/MS1252$Encoder -sun/nio/cs/SingleByteEncoder -java/nio/charset/CharsetEncoder -java/nio/charset/CodingErrorAction -sun/nio/cs/MS1252$Decoder -sun/nio/cs/SingleByteDecoder -java/nio/charset/CharsetDecoder -java/nio/ByteBuffer -java/nio/HeapByteBuffer -java/nio/Bits -java/nio/ByteOrder -java/nio/CharBuffer -java/lang/Readable -java/nio/HeapCharBuffer -java/nio/charset/CoderResult -java/nio/charset/CoderResult$1 -java/nio/charset/CoderResult$Cache -java/nio/charset/CoderResult$2 -sun/nio/cs/Surrogate$Parser -sun/nio/cs/Surrogate -java/io/BufferedWriter -java/lang/Terminator -java/lang/Terminator$1 -sun/misc/SignalHandler -sun/misc/Signal -sun/misc/NativeSignalHandler -java/io/Console -java/io/Console$1 -sun/misc/JavaIOAccess -java/io/Console$1$1 -java/lang/Shutdown -java/util/ArrayList -java/lang/Shutdown$Lock -java/lang/ApplicationShutdownHooks -java/util/IdentityHashMap -sun/misc/OSEnvironment -sun/io/Win32ErrorMode -java/lang/System$2 -sun/misc/JavaLangAccess -java/lang/Compiler -java/lang/Compiler$1 -sun/misc/Launcher -sun/misc/Launcher$Factory -java/net/URLStreamHandlerFactory -sun/misc/Launcher$ExtClassLoader -java/net/URLClassLoader -java/security/SecureClassLoader -sun/security/util/Debug -java/net/URLClassLoader$7 -sun/misc/JavaNetAccess -java/util/StringTokenizer -sun/misc/Launcher$ExtClassLoader$1 -java/security/PrivilegedExceptionAction -sun/misc/MetaIndex -java/io/BufferedReader -java/io/Reader -java/io/FileReader -java/io/InputStreamReader -sun/nio/cs/StreamDecoder -java/lang/reflect/Array -java/util/Locale -java/util/concurrent/ConcurrentHashMap -java/util/concurrent/ConcurrentMap -java/util/concurrent/ConcurrentHashMap$Segment -java/util/concurrent/locks/ReentrantLock -java/util/concurrent/locks/Lock -java/util/concurrent/locks/ReentrantLock$NonfairSync -java/util/concurrent/locks/ReentrantLock$Sync -java/util/concurrent/locks/AbstractQueuedSynchronizer -java/util/concurrent/locks/AbstractOwnableSynchronizer -java/util/concurrent/locks/AbstractQueuedSynchronizer$Node -java/util/concurrent/ConcurrentHashMap$HashEntry -java/lang/CharacterDataLatin1 -java/io/ObjectStreamClass -sun/net/www/ParseUtil -java/util/BitSet -java/net/URL -java/net/Parts -sun/net/www/protocol/file/Handler -java/net/URLStreamHandler -java/util/HashSet -sun/misc/URLClassPath -sun/net/www/protocol/jar/Handler -sun/misc/Launcher$AppClassLoader -sun/misc/Launcher$AppClassLoader$1 -java/lang/SystemClassLoaderAction -java/lang/StringCoding -java/lang/ThreadLocal$ThreadLocalMap -java/lang/ThreadLocal$ThreadLocalMap$Entry -java/lang/StringCoding$StringDecoder -java/net/URLClassLoader$1 -sun/misc/URLClassPath$3 -sun/misc/URLClassPath$JarLoader -sun/misc/URLClassPath$Loader -java/security/PrivilegedActionException -sun/misc/URLClassPath$FileLoader -sun/misc/URLClassPath$FileLoader$1 -sun/misc/Resource -sun/nio/ByteBuffered -java/security/CodeSource -java/security/Permissions -java/security/PermissionCollection -sun/net/www/protocol/file/FileURLConnection -sun/net/www/URLConnection -java/net/URLConnection -java/net/UnknownContentHandler -java/net/ContentHandler -sun/net/www/MessageHeader -java/io/FilePermission -java/io/FilePermission$1 -sun/security/provider/PolicyFile -java/security/Policy -java/security/Policy$UnsupportedEmptyCollection -java/io/FilePermissionCollection -java/security/AllPermission -java/security/UnresolvedPermission -java/security/BasicPermissionCollection -java/security/Principal -java/security/cert/Certificate -java/util/AbstractList$Itr -java/util/IdentityHashMap$KeySet -java/util/IdentityHashMap$KeyIterator -java/util/IdentityHashMap$IdentityHashMapIterator -java/io/DeleteOnExitHook -java/util/LinkedHashSet -java/util/HashMap$KeySet -java/util/LinkedHashMap$KeyIterator -java/util/LinkedHashMap$LinkedHashIterator -java/awt/Frame -java/awt/MenuContainer -java/awt/Window -javax/accessibility/Accessible -java/awt/Container -java/awt/Component -java/awt/image/ImageObserver -java/lang/InterruptedException -java/awt/Label -java/util/logging/Logger -java/util/logging/Handler -java/util/logging/Level -java/util/logging/LogManager -java/util/logging/LogManager$1 -java/beans/PropertyChangeSupport -java/util/logging/LogManager$LogNode -java/util/logging/LoggingPermission -java/util/logging/LogManager$Cleaner -java/util/logging/LogManager$RootLogger -java/util/logging/LogManager$2 -java/util/Properties$LineReader -java/util/Hashtable$Enumerator -java/beans/PropertyChangeEvent -java/util/EventObject -java/awt/Component$AWTTreeLock -sun/awt/DebugHelper -sun/awt/NativeLibLoader -sun/security/action/LoadLibraryAction -sun/awt/DebugHelperStub -java/awt/Toolkit -java/awt/Toolkit$3 -sun/util/CoreResourceBundleControl -java/util/ResourceBundle$Control -java/util/Arrays$ArrayList -java/util/Collections$UnmodifiableRandomAccessList -java/util/Collections$UnmodifiableList -java/util/Collections$UnmodifiableCollection -java/util/ResourceBundle -java/util/ResourceBundle$1 -java/util/ResourceBundle$RBClassLoader -java/util/ResourceBundle$RBClassLoader$1 -java/util/ResourceBundle$CacheKey -java/util/ResourceBundle$LoaderReference -java/util/ResourceBundle$CacheKeyReference -java/util/ResourceBundle$SingleFormatControl -sun/awt/resources/awt -java/util/ListResourceBundle -java/awt/Toolkit$1 -java/io/FileNotFoundException -java/io/IOException -java/awt/GraphicsEnvironment -java/awt/GraphicsEnvironment$1 -java/awt/Insets -sun/awt/windows/WComponentPeer -java/awt/peer/ComponentPeer -java/awt/dnd/peer/DropTargetPeer -sun/awt/DisplayChangedListener -java/util/EventListener -sun/awt/windows/WObjectPeer -java/awt/Font -java/awt/geom/AffineTransform -sun/font/AttributeValues -sun/font/EAttribute -java/lang/Enum -java/text/AttributedCharacterIterator$Attribute -java/lang/Class$4 -sun/reflect/NativeMethodAccessorImpl -sun/reflect/DelegatingMethodAccessorImpl -java/awt/font/TextAttribute -java/lang/Integer$IntegerCache -java/awt/Component$1 -sun/awt/AWTAccessor$ComponentAccessor -sun/awt/AWTAccessor -java/util/WeakHashMap -java/util/WeakHashMap$Entry -java/awt/AWTEvent -java/awt/Component$DummyRequestFocusController -sun/awt/RequestFocusController -java/awt/LayoutManager -java/awt/LightweightDispatcher -java/awt/event/AWTEventListener -java/awt/Dimension -java/awt/geom/Dimension2D -sun/awt/util/IdentityArrayList -java/util/concurrent/atomic/AtomicBoolean -java/awt/Color -java/awt/Paint -java/awt/Transparency -java/awt/Window$1 -sun/awt/AWTAccessor$WindowAccessor -java/awt/ComponentOrientation -java/awt/Component$3 -java/lang/NoSuchMethodException -sun/awt/AppContext -sun/awt/AppContext$1 -sun/awt/AppContext$2 -sun/awt/MostRecentKeyValue -java/awt/Cursor -java/awt/Point -java/awt/geom/Point2D -sun/awt/Win32GraphicsEnvironment -sun/java2d/SunGraphicsEnvironment -sun/java2d/FontSupport -sun/java2d/SunGraphicsEnvironment$1 -sun/java2d/SunGraphicsEnvironment$TTFilter -java/io/FilenameFilter -sun/java2d/SunGraphicsEnvironment$T1Filter -sun/awt/windows/WToolkit -sun/awt/SunToolkit -sun/awt/WindowClosingSupport -sun/awt/WindowClosingListener -sun/awt/ComponentFactory -sun/awt/InputMethodSupport -java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject -java/util/concurrent/locks/Condition -sun/awt/AWTAutoShutdown -sun/awt/AWTAutoShutdown$PeerMap -sun/awt/SunToolkit$6 -java/awt/Dialog$ModalExclusionType -java/awt/Dialog -java/awt/Dialog$ModalityType -java/awt/ModalEventFilter -java/awt/EventFilter -sun/reflect/UnsafeFieldAccessorFactory -sun/awt/windows/WWindowPeer -java/awt/peer/WindowPeer -java/awt/peer/ContainerPeer -sun/awt/windows/WPanelPeer -java/awt/peer/PanelPeer -sun/awt/windows/WCanvasPeer -java/awt/peer/CanvasPeer -sun/awt/windows/WToolkit$5 -java/awt/GraphicsConfiguration -java/awt/image/BufferStrategy -java/awt/dnd/DropTarget -java/awt/dnd/DropTargetListener -java/awt/event/ComponentListener -java/awt/event/FocusListener -java/awt/event/HierarchyListener -java/awt/event/HierarchyBoundsListener -java/awt/event/KeyListener -java/awt/event/MouseListener -java/awt/event/MouseMotionListener -java/awt/event/MouseWheelListener -java/awt/event/InputMethodListener -java/awt/EventQueueItem -java/awt/Component$NativeInLightFixer -java/awt/event/ContainerListener -javax/accessibility/AccessibleContext -sun/awt/windows/WToolkit$6 -java/io/ObjectOutputStream -java/io/ObjectOutput -java/io/DataOutput -java/io/ObjectStreamConstants -java/awt/Shape -java/io/ObjectInputStream -java/io/ObjectInput -java/io/DataInput -java/awt/HeadlessException -java/lang/UnsupportedOperationException -java/awt/image/BufferedImage -java/awt/image/WritableRenderedImage -java/awt/image/RenderedImage -java/awt/Image -java/awt/Rectangle -java/awt/geom/Rectangle2D -java/awt/geom/RectangularShape -java/awt/event/KeyEvent -java/awt/event/InputEvent -java/awt/event/ComponentEvent -java/awt/Event -java/beans/PropertyChangeListener -java/awt/event/WindowFocusListener -java/awt/event/WindowListener -java/awt/event/WindowStateListener -java/awt/BufferCapabilities -java/awt/AWTException -java/awt/event/MouseWheelEvent -java/awt/event/MouseEvent -java/awt/im/InputContext -java/awt/event/WindowEvent -java/lang/SecurityException -sun/reflect/UnsafeQualifiedStaticObjectFieldAccessorImpl -sun/reflect/UnsafeQualifiedStaticFieldAccessorImpl -sun/java2d/SurfaceData -sun/java2d/DisposerTarget -sun/java2d/Surface -sun/java2d/InvalidPipeException -java/lang/IllegalStateException -sun/java2d/NullSurfaceData -sun/java2d/loops/SurfaceType -sun/awt/image/PixelConverter -sun/awt/image/PixelConverter$Xrgb -sun/awt/image/PixelConverter$Argb -sun/awt/image/PixelConverter$ArgbPre -sun/awt/image/PixelConverter$Xbgr -sun/awt/image/PixelConverter$Rgba -sun/awt/image/PixelConverter$RgbaPre -sun/awt/image/PixelConverter$Ushort565Rgb -sun/awt/image/PixelConverter$Ushort555Rgb -sun/awt/image/PixelConverter$Ushort555Rgbx -sun/awt/image/PixelConverter$Ushort4444Argb -sun/awt/image/PixelConverter$ByteGray -sun/awt/image/PixelConverter$UshortGray -sun/awt/image/PixelConverter$Rgbx -sun/awt/image/PixelConverter$Bgrx -sun/awt/image/PixelConverter$ArgbBm -java/awt/image/ColorModel -java/awt/image/DirectColorModel -java/awt/image/PackedColorModel -java/awt/color/ColorSpace -java/awt/color/ICC_Profile -sun/awt/color/ProfileDeferralInfo -sun/awt/color/ProfileDeferralMgr -java/awt/color/ICC_ProfileRGB -java/awt/color/ICC_Profile$1 -sun/awt/color/ProfileActivator -java/awt/color/ICC_ColorSpace -sun/java2d/pipe/NullPipe -sun/java2d/pipe/PixelDrawPipe -sun/java2d/pipe/PixelFillPipe -sun/java2d/pipe/ShapeDrawPipe -sun/java2d/pipe/TextPipe -sun/java2d/pipe/DrawImagePipe -java/awt/image/IndexColorModel -sun/java2d/pipe/LoopPipe -sun/java2d/pipe/OutlineTextRenderer -sun/java2d/pipe/SolidTextRenderer -sun/java2d/pipe/GlyphListLoopPipe -sun/java2d/pipe/GlyphListPipe -sun/java2d/pipe/AATextRenderer -sun/java2d/pipe/LCDTextRenderer -sun/java2d/pipe/AlphaColorPipe -sun/java2d/pipe/CompositePipe -sun/java2d/pipe/PixelToShapeConverter -sun/java2d/pipe/TextRenderer -sun/java2d/pipe/SpanClipRenderer -sun/java2d/pipe/Region -sun/java2d/pipe/RegionIterator -sun/java2d/pipe/AlphaPaintPipe -sun/java2d/pipe/SpanShapeRenderer$Composite -sun/java2d/pipe/SpanShapeRenderer -sun/java2d/pipe/GeneralCompositePipe -sun/java2d/pipe/DrawImage -sun/java2d/loops/RenderCache -sun/java2d/loops/RenderCache$Entry -sun/awt/image/SunVolatileImage -sun/java2d/DestSurfaceProvider -java/awt/image/VolatileImage -java/awt/ImageCapabilities -java/awt/Image$1 -sun/awt/image/SurfaceManager$ImageAccessor -sun/awt/image/SurfaceManager -sun/awt/image/VolatileSurfaceManager -sun/awt/windows/WToolkit$1 -sun/java2d/windows/WindowsFlags -sun/java2d/windows/WindowsFlags$1 -sun/awt/SunDisplayChanger -sun/java2d/SunGraphicsEnvironment$2 -sun/font/FontManager -sun/font/FileFont -sun/font/PhysicalFont -sun/font/Font2D -sun/font/CompositeFont -java/util/HashMap$Values -java/util/HashMap$ValueIterator -java/util/HashMap$HashIterator -sun/font/FontManager$1 -sun/font/TrueTypeFont -java/awt/font/FontRenderContext -java/awt/RenderingHints -sun/awt/SunHints -sun/awt/SunHints$Key -java/awt/RenderingHints$Key -sun/awt/SunHints$Value -sun/awt/SunHints$LCDContrastKey -sun/font/Type1Font -java/awt/geom/Point2D$Float -sun/font/StrikeMetrics -java/awt/geom/Rectangle2D$Float -java/awt/geom/GeneralPath -java/awt/geom/Path2D$Float -java/awt/geom/Path2D -sun/font/CharToGlyphMapper -sun/font/PhysicalStrike -sun/font/FontStrike -sun/font/GlyphList -sun/font/StrikeCache -sun/java2d/Disposer -sun/java2d/Disposer$1 -sun/font/StrikeCache$1 -sun/font/FontManager$FontRegistrationInfo -sun/awt/windows/WFontConfiguration -sun/awt/FontConfiguration -sun/awt/FontDescriptor -java/io/DataInputStream -java/lang/Short$ShortCache -java/util/HashMap$KeyIterator -sun/font/CompositeFontDescriptor -sun/font/Font2DHandle -sun/font/FontFamily -java/awt/GraphicsDevice -sun/java2d/d3d/D3DGraphicsDevice -sun/awt/Win32GraphicsDevice -java/awt/Toolkit$2 -java/awt/Toolkit$DesktopPropertyChangeSupport -sun/awt/SunToolkit$ModalityListenerList -sun/awt/ModalityListener -sun/awt/SunToolkit$1 -java/util/MissingResourceException -java/awt/EventQueue -java/awt/Queue -sun/awt/PostEventQueue -sun/misc/PerformanceLogger -sun/misc/PerformanceLogger$TimeData -sun/awt/windows/WToolkit$ToolkitDisposer -sun/java2d/DisposerRecord -sun/awt/windows/WToolkit$2 -sun/awt/windows/WToolkit$3 -sun/java2d/d3d/D3DRenderQueue -sun/java2d/pipe/RenderQueue -sun/java2d/pipe/RenderBuffer -sun/java2d/d3d/D3DRenderQueue$1 -sun/java2d/d3d/D3DGraphicsDevice$1Result -sun/java2d/d3d/D3DGraphicsDevice$1 -sun/java2d/d3d/D3DContext$D3DContextCaps -sun/java2d/pipe/hw/ContextCapabilities -sun/java2d/d3d/D3DContext -sun/java2d/pipe/BufferedContext -sun/java2d/d3d/D3DGraphicsConfig -sun/java2d/pipe/hw/AccelGraphicsConfig -sun/java2d/pipe/hw/BufferedContextProvider -sun/awt/Win32GraphicsConfig -sun/java2d/d3d/D3DGraphicsConfig$D3DImageCaps -java/awt/BorderLayout -java/awt/LayoutManager2 -java/awt/Window$WindowDisposerRecord -java/awt/KeyboardFocusManager -java/awt/KeyEventDispatcher -java/awt/KeyEventPostProcessor -java/awt/event/NativeLibLoader -java/awt/AWTKeyStroke -java/awt/AWTKeyStroke$1 -java/util/LinkedList -java/util/Deque -java/util/Queue -java/util/AbstractSequentialList -java/util/LinkedList$Entry -java/awt/DefaultKeyboardFocusManager -java/awt/DefaultFocusTraversalPolicy -java/awt/ContainerOrderFocusTraversalPolicy -java/awt/FocusTraversalPolicy -java/awt/MutableBoolean -java/util/Collections$UnmodifiableSet -sun/awt/HeadlessToolkit -sun/awt/KeyboardFocusManagerPeerImpl -java/awt/peer/KeyboardFocusManagerPeer -sun/awt/windows/WFramePeer -java/awt/peer/FramePeer -sun/awt/RepaintArea -sun/awt/EmbeddedFrame -sun/awt/im/InputMethodWindow -sun/awt/windows/WComponentPeer$2 -sun/awt/PaintEventDispatcher -java/awt/event/InvocationEvent -java/awt/ActiveEvent -java/awt/MenuComponent -sun/awt/EventQueueItem -sun/awt/SunToolkit$3 -java/util/EmptyStackException -java/lang/reflect/InvocationTargetException -java/awt/event/PaintEvent -java/awt/EventDispatchThread -sun/awt/PeerEvent -java/awt/EventQueue$1 -sun/java2d/ScreenUpdateManager -java/awt/EventDispatchThread$1 -sun/java2d/d3d/D3DScreenUpdateManager$1 -sun/awt/EventQueueDelegate -java/awt/EventFilter$FilterAction -com/sun/awt/AWTUtilities -sun/java2d/d3d/D3DSurfaceData -sun/java2d/d3d/D3DDrawImage -sun/java2d/d3d/D3DTextRenderer -java/awt/event/ActionEvent -sun/java2d/d3d/D3DRenderer -sun/java2d/pipe/BufferedRenderPipe -sun/java2d/pipe/ParallelogramPipe -sun/java2d/pipe/BufferedRenderPipe$AAParallelogramPipe -sun/java2d/pipe/BufferedRenderPipe$BufferedDrawHandler -sun/java2d/loops/ProcessPath$DrawHandler -sun/java2d/loops/GraphicsPrimitive -sun/java2d/pipe/PixelToParallelogramConverter -sun/java2d/d3d/D3DBlitLoops -sun/java2d/d3d/D3DSwToSurfaceBlit -sun/java2d/loops/Blit -sun/java2d/loops/GraphicsPrimitiveMgr -sun/java2d/loops/CompositeType -sun/java2d/SunGraphics2D -sun/awt/ConstrainableGraphics -java/awt/Graphics2D -java/awt/Graphics -sun/java2d/loops/XORComposite -java/awt/Composite -java/awt/AlphaComposite -sun/java2d/loops/BlitBg -sun/java2d/loops/ScaledBlit -sun/java2d/loops/FillRect -sun/java2d/loops/FillSpans -sun/java2d/loops/DrawLine -sun/java2d/loops/DrawRect -sun/java2d/loops/DrawPolygons -sun/java2d/loops/DrawPath -sun/java2d/loops/FillPath -sun/java2d/loops/MaskBlit -sun/java2d/loops/MaskFill -sun/java2d/loops/DrawGlyphList -sun/java2d/loops/DrawGlyphListAA -sun/java2d/loops/DrawGlyphListLCD -sun/java2d/loops/TransformHelper -java/awt/BasicStroke -java/awt/Stroke -sun/java2d/pipe/ValidatePipe -sun/java2d/loops/CustomComponent -sun/java2d/loops/GraphicsPrimitiveProxy -sun/java2d/loops/GeneralRenderer -sun/java2d/loops/GraphicsPrimitiveMgr$1 -sun/java2d/loops/GraphicsPrimitiveMgr$2 -sun/java2d/d3d/D3DSwToTextureBlit -sun/java2d/d3d/D3DSurfaceToGDIWindowSurfaceBlit -sun/java2d/windows/GDIWindowSurfaceData -sun/java2d/windows/GDIBlitLoops -sun/java2d/windows/GDIRenderer -sun/java2d/d3d/D3DSurfaceToGDIWindowSurfaceScale -sun/java2d/d3d/D3DSurfaceToGDIWindowSurfaceTransform -sun/java2d/loops/TransformBlit -sun/java2d/d3d/D3DSurfaceToSurfaceBlit -sun/java2d/d3d/D3DSurfaceToSurfaceScale -sun/java2d/d3d/D3DSurfaceToSurfaceTransform -sun/java2d/d3d/D3DRTTSurfaceToSurfaceBlit -sun/java2d/d3d/D3DRTTSurfaceToSurfaceScale -sun/java2d/d3d/D3DRTTSurfaceToSurfaceTransform -sun/java2d/d3d/D3DSurfaceToSwBlit -sun/java2d/d3d/D3DGeneralBlit -sun/java2d/d3d/D3DSwToSurfaceScale -sun/java2d/d3d/D3DSwToSurfaceTransform -sun/java2d/d3d/D3DTextureToSurfaceBlit -sun/java2d/d3d/D3DTextureToSurfaceScale -sun/java2d/d3d/D3DTextureToSurfaceTransform -sun/java2d/d3d/D3DMaskFill -sun/java2d/pipe/BufferedMaskFill -sun/java2d/d3d/D3DMaskBlit -sun/java2d/pipe/BufferedMaskBlit -sun/java2d/d3d/D3DSurfaceData$D3DWindowSurfaceData -sun/java2d/pipe/hw/ExtendedBufferCapabilities$VSyncType -sun/java2d/DefaultDisposerRecord -sun/security/action/GetBooleanAction -sun/java2d/d3d/D3DScreenUpdateManager$2 -sun/awt/windows/WColor -sun/awt/windows/WFontPeer -sun/awt/PlatformFont -java/awt/peer/FontPeer -sun/awt/FontConfiguration$1 -sun/awt/windows/WingDings -sun/awt/windows/WingDings$Encoder -sun/awt/Symbol -sun/awt/Symbol$Encoder -sun/awt/im/InputMethodManager -sun/awt/im/ExecutableInputMethodManager -sun/awt/windows/WInputMethodDescriptor -java/awt/im/spi/InputMethodDescriptor -sun/awt/im/InputMethodLocator -sun/awt/im/ExecutableInputMethodManager$3 -sun/misc/Service -sun/misc/Service$LazyIterator -java/util/TreeSet -java/util/NavigableSet -java/util/SortedSet -java/util/TreeMap -java/util/NavigableMap -java/util/SortedMap -sun/misc/Launcher$1 -sun/misc/Launcher$2 -sun/misc/URLClassPath$2 -java/lang/ClassLoader$2 -sun/misc/URLClassPath$1 -java/net/URLClassLoader$3 -sun/misc/CompoundEnumeration -sun/misc/URLClassPath$JarLoader$1 -sun/misc/FileURLMapper -java/net/URLClassLoader$3$1 -sun/awt/SunToolkit$2 -sun/reflect/UnsafeObjectFieldAccessorImpl -java/awt/peer/LightweightPeer -sun/awt/windows/WLabelPeer -java/awt/peer/LabelPeer -sun/java2d/loops/RenderLoops -sun/java2d/loops/GraphicsPrimitiveMgr$PrimitiveSpec -sun/awt/windows/WFileDialogPeer -java/awt/peer/FileDialogPeer -java/awt/peer/DialogPeer -sun/awt/windows/WPrintDialogPeer -sun/awt/dnd/SunDropTargetEvent -java/awt/PopupMenu -java/awt/event/FocusEvent -java/awt/Menu -java/awt/MenuItem -java/io/PrintWriter -sun/awt/CausedFocusEvent$Cause -java/awt/PointerInfo -java/awt/image/ImageProducer -javax/accessibility/AccessibleStateSet -java/awt/Component$BaselineResizeBehavior -java/awt/FontMetrics -java/awt/im/InputMethodRequests -java/awt/event/HierarchyEvent -java/awt/SequencedEvent -sun/awt/windows/WGlobalCursorManager -sun/awt/PlatformFont$PlatformFontCache -sun/nio/cs/Unicode -sun/nio/cs/UTF_16LE$Encoder -sun/nio/cs/UnicodeEncoder -sun/nio/cs/UTF_16LE$Decoder -sun/nio/cs/UnicodeDecoder -sun/awt/event/IgnorePaintEvent -java/awt/KeyboardFocusManager$HeavyweightFocusRequest -java/util/LinkedList$ListItr -java/util/ListIterator -java/awt/DefaultKeyboardFocusManager$TypeAheadMarker -java/awt/KeyboardFocusManager$LightweightFocusRequest -sun/reflect/MethodAccessorGenerator -sun/reflect/AccessorGenerator -sun/reflect/ClassFileConstants -sun/reflect/ByteVectorFactory -sun/reflect/ByteVectorImpl -sun/reflect/ByteVector -sun/reflect/ClassFileAssembler -sun/reflect/UTF8 -java/lang/Void -sun/reflect/Label -sun/reflect/Label$PatchInfo -sun/reflect/MethodAccessorGenerator$1 -sun/reflect/ClassDefiner -sun/reflect/ClassDefiner$1 -sun/java2d/pipe/hw/AccelDeviceEventNotifier -javax/swing/JFrame -javax/swing/WindowConstants -javax/swing/RootPaneContainer -javax/swing/TransferHandler$HasGetTransferHandler -javax/swing/JLabel -javax/swing/SwingConstants -javax/swing/JComponent -javax/swing/JComponent$1 -javax/swing/SwingUtilities -javax/swing/JRootPane -javax/swing/event/EventListenerList -javax/swing/JPanel -java/awt/FlowLayout -javax/swing/UIManager -javax/swing/UIManager$LookAndFeelInfo -sun/awt/shell/Win32ShellFolderManager2 -sun/awt/shell/ShellFolderManager -sun/awt/windows/WDesktopProperties -sun/awt/windows/ThemeReader -java/util/concurrent/locks/ReentrantReadWriteLock -java/util/concurrent/locks/ReadWriteLock -java/util/concurrent/locks/ReentrantReadWriteLock$NonfairSync -java/util/concurrent/locks/ReentrantReadWriteLock$Sync -java/util/concurrent/locks/ReentrantReadWriteLock$Sync$ThreadLocalHoldCounter -java/util/concurrent/locks/ReentrantReadWriteLock$ReadLock -java/util/concurrent/locks/ReentrantReadWriteLock$WriteLock -sun/awt/windows/WDesktopProperties$WinPlaySound -java/util/HashMap$EntrySet -java/util/HashMap$EntryIterator -java/awt/Toolkit$DesktopPropertyChangeSupport$1 -java/util/Collections$SynchronizedCollection -java/util/IdentityHashMap$Values -java/util/IdentityHashMap$ValueIterator -sun/swing/SwingUtilities2 -sun/swing/SwingUtilities2$LSBCacheEntry -javax/swing/UIManager$LAFState -javax/swing/UIDefaults -javax/swing/MultiUIDefaults -javax/swing/UIManager$1 -javax/swing/plaf/metal/MetalLookAndFeel -javax/swing/plaf/basic/BasicLookAndFeel -javax/swing/LookAndFeel -sun/swing/DefaultLookup -javax/swing/plaf/metal/OceanTheme -javax/swing/plaf/metal/DefaultMetalTheme -javax/swing/plaf/metal/MetalTheme -javax/swing/plaf/ColorUIResource -javax/swing/plaf/UIResource -sun/swing/PrintColorUIResource -javax/swing/plaf/metal/DefaultMetalTheme$FontDelegate -javax/swing/plaf/FontUIResource -sun/swing/SwingLazyValue -javax/swing/UIDefaults$LazyValue -javax/swing/UIDefaults$ActiveValue -javax/swing/plaf/InsetsUIResource -sun/swing/SwingUtilities2$2 -javax/swing/plaf/basic/BasicLookAndFeel$2 -javax/swing/plaf/DimensionUIResource -javax/swing/UIDefaults$LazyInputMap -java/lang/Character$CharacterCache -javax/swing/plaf/metal/MetalLookAndFeel$MetalLazyValue -javax/swing/plaf/metal/MetalLookAndFeel$FontActiveValue -sun/swing/SwingUtilities2$AATextInfo -javax/swing/plaf/metal/MetalLookAndFeel$AATextListener -java/beans/PropertyChangeListenerProxy -java/util/EventListenerProxy -sun/awt/EventListenerAggregate -javax/swing/UIDefaults$ProxyLazyValue -javax/swing/plaf/metal/OceanTheme$1 -javax/swing/plaf/metal/OceanTheme$2 -javax/swing/plaf/metal/OceanTheme$3 -javax/swing/plaf/metal/OceanTheme$4 -javax/swing/plaf/metal/OceanTheme$5 -javax/swing/plaf/metal/OceanTheme$6 -javax/swing/FocusManager -javax/swing/LayoutFocusTraversalPolicy -javax/swing/SortingFocusTraversalPolicy -javax/swing/InternalFrameFocusTraversalPolicy -javax/swing/SwingContainerOrderFocusTraversalPolicy -javax/swing/SwingDefaultFocusTraversalPolicy -javax/swing/LayoutComparator -javax/swing/RepaintManager -javax/swing/RepaintManager$DisplayChangedHandler -javax/swing/SwingPaintEventDispatcher -javax/swing/UIManager$2 -javax/swing/UIManager$3 -java/awt/event/InputMethodEvent -com/sun/swing/internal/plaf/metal/resources/metal -sun/util/ResourceBundleEnumeration -com/sun/swing/internal/plaf/basic/resources/basic -javax/swing/plaf/basic/BasicPanelUI -javax/swing/plaf/PanelUI -javax/swing/plaf/ComponentUI -sun/reflect/misc/MethodUtil -sun/reflect/misc/MethodUtil$1 -sun/awt/AppContext$PostShutdownEventRunnable -sun/awt/AWTAutoShutdown$1 -java/util/jar/JarFile -java/util/zip/ZipFile -java/util/zip/ZipConstants -java/util/jar/JavaUtilJarAccessImpl -sun/misc/JavaUtilJarAccess -sun/misc/JarIndex -java/util/zip/ZipEntry -java/util/jar/JarFile$JarFileEntry -java/util/jar/JarEntry -sun/misc/URLClassPath$JarLoader$2 -sun/net/www/protocol/jar/JarURLConnection -java/net/JarURLConnection -sun/net/www/protocol/jar/JarFileFactory -sun/net/www/protocol/jar/URLJarFile$URLJarFileCloseController -java/net/HttpURLConnection -sun/net/www/protocol/jar/URLJarFile -sun/net/www/protocol/jar/URLJarFile$URLJarFileEntry -sun/net/www/protocol/jar/JarURLConnection$JarURLInputStream -java/util/zip/ZipFile$ZipFileInputStream -java/security/AllPermissionCollection -java/lang/IllegalAccessException -com/sun/java/swing/SwingUtilities3 -javax/swing/JPasswordField -javax/swing/JTextField -javax/swing/text/JTextComponent -javax/swing/Scrollable -javax/swing/JLayeredPane -javax/swing/JRootPane$1 -javax/swing/ArrayTable -javax/swing/JInternalFrame -javax/swing/JRootPane$RootLayout -javax/swing/BufferStrategyPaintManager -javax/swing/RepaintManager$PaintManager -javax/swing/plaf/metal/MetalRootPaneUI -javax/swing/plaf/basic/BasicRootPaneUI -javax/swing/plaf/RootPaneUI -javax/swing/plaf/basic/BasicRootPaneUI$RootPaneInputMap -javax/swing/plaf/ComponentInputMapUIResource -javax/swing/ComponentInputMap -javax/swing/InputMap -javax/swing/plaf/InputMapUIResource -javax/swing/KeyStroke -java/awt/VKCollection -sun/reflect/UnsafeQualifiedStaticIntegerFieldAccessorImpl -javax/swing/plaf/basic/LazyActionMap -javax/swing/plaf/ActionMapUIResource -javax/swing/ActionMap -javax/swing/plaf/metal/MetalLabelUI -javax/swing/plaf/basic/BasicLabelUI -javax/swing/plaf/LabelUI -javax/swing/plaf/metal/DefaultMetalTheme$FontDelegate$1 -javax/swing/plaf/basic/BasicHTML -javax/swing/SystemEventQueueUtilities -javax/swing/SystemEventQueueUtilities$ComponentWorkRequest -java/awt/Conditional -sun/java2d/d3d/D3DScreenUpdateManager -sun/java2d/d3d/D3DScreenUpdateManager$1$1 -java/awt/EventDispatchThread$HierarchyEventFilter -sun/awt/windows/WEmbeddedFrame -sun/awt/dnd/SunDragSourceContextPeer -sun/java2d/pipe/hw/AccelSurface -sun/java2d/pipe/BufferedTextPipe -javax/swing/SystemEventQueueUtilities$SystemEventQueue -sun/awt/NullComponentPeer -sun/awt/GlobalCursorManager$NativeUpdater -java/awt/SentEvent -java/util/jar/Manifest -java/io/ByteArrayInputStream -java/util/jar/Attributes -java/util/jar/Manifest$FastInputStream -sun/nio/cs/UTF_8 -sun/nio/cs/UTF_8$Decoder -sun/nio/cs/Surrogate$Generator -java/util/jar/Attributes$Name -sun/misc/ASCIICaseInsensitiveComparator -java/util/jar/JarVerifier -java/io/ByteArrayOutputStream -sun/misc/ExtensionDependency -java/lang/Package -sun/security/util/ManifestEntryVerifier -sun/security/provider/Sun -java/security/Provider -java/security/Provider$ServiceKey -java/security/Provider$EngineDescription -sun/security/provider/Sun$1 -java/security/Security -java/security/Security$1 -sun/misc/FloatingDecimal -sun/misc/FloatingDecimal$1 -java/util/regex/Pattern -java/util/regex/Pattern$5 -java/util/regex/Pattern$Node -java/util/regex/Pattern$LastNode -java/util/regex/Pattern$GroupHead -java/util/regex/Pattern$GroupTail -java/util/regex/Pattern$BitClass -java/util/regex/Pattern$BmpCharProperty -java/util/regex/Pattern$CharProperty -java/util/regex/Pattern$Ques -java/util/regex/Pattern$BranchConn -java/util/regex/Pattern$Branch -java/util/regex/Pattern$Single -java/util/regex/Pattern$CharPropertyNames -java/util/regex/Pattern$CharPropertyNames$1 -java/util/regex/Pattern$CharPropertyNames$CharPropertyFactory -java/util/regex/Pattern$CharPropertyNames$2 -java/util/regex/Pattern$CharPropertyNames$5 -java/util/regex/Pattern$CharPropertyNames$3 -java/util/regex/Pattern$CharPropertyNames$6 -java/util/regex/Pattern$CharPropertyNames$CloneableProperty -java/util/regex/Pattern$CharPropertyNames$4 -java/util/regex/Pattern$CharPropertyNames$7 -java/util/regex/Pattern$CharPropertyNames$8 -java/util/regex/Pattern$CharPropertyNames$9 -java/util/regex/Pattern$CharPropertyNames$10 -java/util/regex/Pattern$CharPropertyNames$11 -java/util/regex/Pattern$CharPropertyNames$12 -java/util/regex/Pattern$CharPropertyNames$13 -java/util/regex/Pattern$CharPropertyNames$14 -java/util/regex/Pattern$CharPropertyNames$15 -java/util/regex/Pattern$CharPropertyNames$16 -java/util/regex/Pattern$CharPropertyNames$17 -java/util/regex/Pattern$CharPropertyNames$18 -java/util/regex/Pattern$CharPropertyNames$19 -java/util/regex/Pattern$CharPropertyNames$20 -java/util/regex/Pattern$CharPropertyNames$21 -java/util/regex/Pattern$Ctype -java/util/regex/Pattern$Curly -java/util/regex/Pattern$2 -java/util/regex/Pattern$Slice -java/util/regex/Pattern$SliceNode -java/util/regex/Pattern$Begin -java/util/regex/Pattern$First -java/util/regex/Pattern$Start -java/util/regex/Pattern$TreeInfo -java/lang/StrictMath -sun/security/provider/NativePRNG -sun/misc/BASE64Decoder -sun/misc/CharacterDecoder -sun/security/util/SignatureFileVerifier -java/awt/event/KeyAdapter -java/lang/NumberFormatException -java/lang/IllegalArgumentException -java/io/FileWriter -java/net/Authenticator -java/net/MalformedURLException -javax/swing/text/Element -javax/swing/text/Document -javax/swing/text/PlainDocument -javax/swing/text/AbstractDocument -javax/swing/text/GapContent -javax/swing/text/AbstractDocument$Content -javax/swing/text/GapVector -javax/swing/text/GapContent$MarkVector -javax/swing/text/GapContent$MarkData -javax/swing/text/StyleContext -javax/swing/text/AbstractDocument$AttributeContext -javax/swing/text/StyleConstants -javax/swing/text/StyleConstants$CharacterConstants -javax/swing/text/AttributeSet$CharacterAttribute -javax/swing/text/StyleConstants$FontConstants -javax/swing/text/AttributeSet$FontAttribute -javax/swing/text/StyleConstants$ColorConstants -javax/swing/text/AttributeSet$ColorAttribute -javax/swing/text/StyleConstants$ParagraphConstants -javax/swing/text/AttributeSet$ParagraphAttribute -javax/swing/text/StyleContext$FontKey -javax/swing/text/SimpleAttributeSet -javax/swing/text/MutableAttributeSet -javax/swing/text/AttributeSet -javax/swing/text/SimpleAttributeSet$EmptyAttributeSet -javax/swing/text/StyleContext$NamedStyle -javax/swing/text/Style -javax/swing/text/SimpleAttributeSet$1 -javax/swing/text/StyleContext$SmallAttributeSet -javax/swing/text/AbstractDocument$BidiRootElement -javax/swing/text/AbstractDocument$BranchElement -javax/swing/text/AbstractDocument$AbstractElement -javax/swing/tree/TreeNode -javax/swing/text/AbstractDocument$1 -javax/swing/text/AbstractDocument$BidiElement -javax/swing/text/AbstractDocument$LeafElement -javax/swing/text/GapContent$StickyPosition -javax/swing/text/Position -javax/swing/text/StyleContext$KeyEnumeration -javax/swing/text/GapContent$InsertUndo -javax/swing/undo/AbstractUndoableEdit -javax/swing/undo/UndoableEdit -javax/swing/text/AbstractDocument$DefaultDocumentEvent -javax/swing/event/DocumentEvent -javax/swing/undo/CompoundEdit -javax/swing/event/DocumentEvent$EventType -javax/swing/text/Segment -java/text/CharacterIterator -javax/swing/text/Utilities -javax/swing/text/SegmentCache -javax/swing/text/SegmentCache$CachedSegment -javax/swing/event/UndoableEditEvent -javax/swing/text/AbstractDocument$ElementEdit -javax/swing/event/DocumentEvent$ElementChange -sun/misc/Cleaner -javax/swing/JMenu -javax/swing/MenuElement -javax/swing/JMenuItem -javax/swing/AbstractButton -java/awt/ItemSelectable -javax/swing/event/MenuListener -javax/swing/JCheckBoxMenuItem -javax/swing/Icon -javax/swing/JButton -java/net/URLClassLoader$2 -javax/swing/ImageIcon -javax/swing/ImageIcon$1 -javax/swing/ImageIcon$2 -java/awt/MediaTracker -sun/misc/SoftCache$ValueCell -sun/awt/image/URLImageSource -sun/awt/image/InputStreamImageSource -sun/awt/image/ImageFetchable -sun/awt/image/ToolkitImage -sun/awt/image/NativeLibLoader -javax/swing/ImageIcon$3 -java/awt/ImageMediaEntry -java/awt/MediaEntry -sun/awt/image/ImageRepresentation -java/awt/image/ImageConsumer -sun/awt/image/ImageWatched -sun/awt/image/ImageWatched$Link -sun/awt/image/ImageWatched$WeakLink -sun/awt/image/ImageConsumerQueue -sun/awt/image/ImageFetcher -sun/awt/image/FetcherInfo -sun/awt/image/ImageFetcher$1 -sun/awt/image/GifImageDecoder -sun/awt/image/ImageDecoder -sun/awt/image/GifFrame -java/awt/image/Raster -java/awt/image/DataBufferByte -java/awt/image/DataBuffer -java/awt/image/PixelInterleavedSampleModel -java/awt/image/ComponentSampleModel -java/awt/image/SampleModel -sun/awt/image/ByteInterleavedRaster -sun/awt/image/ByteComponentRaster -sun/awt/image/SunWritableRaster -java/awt/image/WritableRaster -sun/awt/image/IntegerComponentRaster -sun/awt/image/BytePackedRaster -java/awt/Canvas -sun/font/FontDesignMetrics -sun/font/FontStrikeDesc -sun/font/CompositeStrike -sun/font/FontStrikeDisposer -sun/font/StrikeCache$SoftDisposerRef -sun/font/StrikeCache$DisposableStrike -sun/font/TrueTypeFont$TTDisposerRecord -sun/font/TrueTypeFont$1 -java/io/RandomAccessFile -sun/nio/ch/FileChannelImpl -java/nio/channels/FileChannel -java/nio/channels/ByteChannel -java/nio/channels/ReadableByteChannel -java/nio/channels/Channel -java/nio/channels/WritableByteChannel -java/nio/channels/GatheringByteChannel -java/nio/channels/ScatteringByteChannel -java/nio/channels/spi/AbstractInterruptibleChannel -java/nio/channels/InterruptibleChannel -sun/nio/ch/Util -sun/nio/ch/IOUtil -sun/nio/ch/FileDispatcher -sun/nio/ch/NativeDispatcher -sun/nio/ch/Reflect -java/nio/MappedByteBuffer -sun/nio/ch/Reflect$1 -sun/nio/ch/NativeThreadSet -java/nio/channels/spi/AbstractInterruptibleChannel$1 -sun/nio/ch/Interruptible -sun/nio/ch/NativeThread -sun/nio/ch/IOStatus -sun/nio/ch/DirectBuffer -java/nio/DirectByteBuffer -java/nio/DirectByteBuffer$Deallocator -java/nio/ByteBufferAsIntBufferB -java/nio/IntBuffer -sun/font/TrueTypeFont$DirectoryEntry -java/nio/ByteBufferAsShortBufferB -java/nio/ShortBuffer -sun/nio/cs/UTF_16 -sun/nio/cs/UTF_16$Decoder -sun/font/FileFontStrike -sun/font/FileFont$FileFontDisposer -sun/font/TrueTypeGlyphMapper -sun/font/CMap -sun/font/CMap$NullCMapClass -sun/font/CMap$CMapFormat4 -java/nio/ByteBufferAsCharBufferB -sun/font/FontDesignMetrics$KeyReference -sun/awt/image/PNGImageDecoder -sun/awt/image/PNGFilterInputStream -java/util/zip/InflaterInputStream -java/util/zip/Inflater -java/awt/dnd/peer/DragSourceContextPeer -javax/swing/Popup$HeavyWeightWindow -sun/awt/ModalExclude -javax/swing/JWindow -com/sun/java/swing/plaf/windows/WindowsPopupWindow -sun/awt/GlobalCursorManager -sun/java2d/d3d/D3DSurfaceData$1Status -sun/java2d/d3d/D3DSurfaceData$1 -sun/java2d/loops/SetDrawLineANY -sun/java2d/loops/SetFillRectANY -sun/java2d/loops/SetDrawRectANY -sun/java2d/loops/SetDrawPolygonsANY -sun/java2d/loops/SetDrawPathANY -sun/java2d/loops/SetFillPathANY -sun/java2d/loops/SetFillSpansANY -sun/java2d/loops/DrawGlyphList$General -sun/java2d/loops/DrawGlyphListAA$General -sun/java2d/pipe/BufferedPaints -sun/java2d/d3d/D3DScreenUpdateManager$3 -java/awt/image/DataBufferInt -java/awt/image/SinglePixelPackedSampleModel -sun/awt/image/IntegerInterleavedRaster -sun/awt/image/OffScreenImage -sun/java2d/SurfaceManagerFactory -sun/java2d/d3d/D3DCachingSurfaceManager -sun/awt/image/CachingSurfaceManager -sun/awt/image/RasterListener -sun/awt/image/BufImgSurfaceData -sun/font/CompositeGlyphMapper -sun/java2d/loops/FontInfo -java/util/Date -sun/util/calendar/CalendarSystem -sun/util/calendar/Gregorian -sun/util/calendar/BaseCalendar -sun/util/calendar/AbstractCalendar -java/util/TimeZone -java/lang/InheritableThreadLocal -sun/util/calendar/ZoneInfo -sun/util/calendar/ZoneInfoFile -sun/util/calendar/ZoneInfoFile$1 -java/util/TimeZone$1 -sun/util/calendar/Gregorian$Date -sun/util/calendar/BaseCalendar$Date -sun/util/calendar/CalendarDate -sun/util/calendar/CalendarUtils -java/util/TimeZone$DisplayNames -sun/util/TimeZoneNameUtility -sun/util/resources/LocaleData -sun/util/resources/LocaleData$1 -sun/util/resources/LocaleData$LocaleDataResourceBundleControl -sun/util/LocaleDataMetaInfo -sun/util/resources/TimeZoneNames -sun/util/resources/TimeZoneNamesBundle -sun/util/resources/OpenListResourceBundle -java/util/ResourceBundle$BundleReference -sun/util/resources/TimeZoneNames_en -java/util/spi/TimeZoneNameProvider -java/util/spi/LocaleServiceProvider -sun/util/LocaleServiceProviderPool -sun/util/LocaleServiceProviderPool$1 -java/util/ServiceLoader -java/util/ServiceLoader$LazyIterator -java/util/ServiceLoader$1 -java/util/LinkedHashMap$EntryIterator -java/net/ServerSocket -java/net/InetAddress -java/net/InetAddress$Cache -java/net/InetAddress$Cache$Type -java/net/InetAddressImplFactory -java/net/Inet4AddressImpl -java/net/InetAddressImpl -java/net/InetAddress$1 -sun/net/spi/nameservice/NameService -sun/net/util/IPAddressUtil -java/util/regex/Matcher -java/util/regex/MatchResult -java/util/RandomAccessSubList -java/util/SubList -java/util/SubList$1 -java/util/AbstractList$ListItr -java/net/Inet4Address -java/net/SocksSocketImpl -java/net/SocksConsts -java/net/PlainSocketImpl -java/net/SocketImpl -java/net/SocketOptions -java/net/InetSocketAddress -java/net/SocketAddress -java/util/Random -java/util/concurrent/atomic/AtomicLong -java/lang/InternalError -java/io/StringReader -java/io/FilterReader -java/lang/reflect/Proxy -java/lang/reflect/InvocationHandler -java/lang/NoSuchFieldException -java/lang/InstantiationException -java/lang/ArrayIndexOutOfBoundsException -java/lang/IndexOutOfBoundsException -javax/swing/JDialog -java/io/EOFException -java/util/Vector$1 -javax/swing/filechooser/FileSystemView -javax/swing/filechooser/FileSystemView$1 -javax/swing/event/SwingPropertyChangeSupport -javax/swing/filechooser/WindowsFileSystemView -java/util/zip/ZipFile$1 -java/util/zip/ZipFile$2 -java/util/jar/JarFile$1 -java/util/PropertyResourceBundle -java/util/ResourceBundle$Control$1 -java/util/Hashtable$EntrySet -java/util/Collections$SynchronizedSet -java/lang/IllegalAccessError -java/text/MessageFormat -java/text/Format -java/text/FieldPosition -java/text/MessageFormat$Field -java/text/Format$Field -java/lang/CloneNotSupportedException -sun/reflect/BootstrapConstructorAccessorImpl -java/awt/event/ActionListener -javax/swing/Timer -javax/swing/Timer$DoPostEvent -javax/swing/TimerQueue -javax/swing/TimerQueue$1 -javax/swing/ToolTipManager -java/awt/event/MouseAdapter -javax/swing/ToolTipManager$insideTimerAction -javax/swing/ToolTipManager$outsideTimerAction -javax/swing/ToolTipManager$stillInsideTimerAction -javax/swing/ToolTipManager$Actions -sun/swing/UIAction -javax/swing/Action -javax/swing/ToolTipManager$MoveBeforeEnterListener -java/awt/event/MouseMotionAdapter -java/util/Hashtable$ValueCollection -javax/swing/event/CaretListener -javax/swing/JToolBar -javax/swing/JSplitPane -javax/swing/border/Border -javax/swing/JToggleButton -javax/swing/border/EmptyBorder -javax/swing/border/AbstractBorder -javax/swing/DefaultButtonModel -javax/swing/ButtonModel -javax/swing/AbstractButton$Handler -javax/swing/event/ChangeListener -java/awt/event/ItemListener -javax/swing/plaf/metal/MetalButtonUI -javax/swing/plaf/basic/BasicButtonUI -javax/swing/plaf/ButtonUI -javax/swing/plaf/metal/MetalBorders -javax/swing/plaf/BorderUIResource$CompoundBorderUIResource -javax/swing/border/CompoundBorder -javax/swing/plaf/metal/MetalBorders$ButtonBorder -javax/swing/plaf/basic/BasicBorders$MarginBorder -javax/swing/plaf/basic/BasicButtonListener -java/awt/AWTEventMulticaster -java/awt/event/AdjustmentListener -java/awt/event/TextListener -javax/swing/event/AncestorListener -java/beans/VetoableChangeListener -javax/swing/ButtonGroup -javax/swing/JToggleButton$ToggleButtonModel -javax/swing/plaf/metal/MetalToggleButtonUI -javax/swing/plaf/basic/BasicToggleButtonUI -javax/swing/plaf/metal/MetalBorders$ToggleButtonBorder -java/awt/CardLayout -javax/swing/Box -javax/swing/plaf/metal/MetalBorders$TextFieldBorder -javax/swing/plaf/metal/MetalBorders$Flush3DBorder -javax/swing/BoxLayout -javax/swing/JMenuBar -javax/swing/DefaultSingleSelectionModel -javax/swing/SingleSelectionModel -javax/swing/plaf/basic/BasicMenuBarUI -javax/swing/plaf/MenuBarUI -javax/swing/plaf/basic/DefaultMenuLayout -javax/swing/plaf/metal/MetalBorders$MenuBarBorder -javax/swing/plaf/basic/BasicMenuBarUI$Handler -javax/swing/KeyboardManager -javax/swing/event/MenuEvent -javax/swing/JMenu$MenuChangeListener -javax/swing/JMenuItem$MenuItemFocusListener -javax/swing/plaf/basic/BasicMenuUI -javax/swing/plaf/basic/BasicMenuItemUI -javax/swing/plaf/MenuItemUI -javax/swing/plaf/metal/MetalBorders$MenuItemBorder -javax/swing/plaf/metal/MetalIconFactory -javax/swing/plaf/metal/MetalIconFactory$MenuArrowIcon -javax/swing/plaf/basic/BasicMenuUI$Handler -javax/swing/event/MenuKeyListener -javax/swing/plaf/basic/BasicMenuItemUI$Handler -javax/swing/event/MenuDragMouseListener -javax/swing/event/MouseInputListener -javax/swing/event/ChangeEvent -java/awt/event/ContainerEvent -javax/swing/plaf/metal/MetalIconFactory$MenuItemArrowIcon -javax/swing/JPopupMenu -javax/swing/plaf/basic/BasicPopupMenuUI -javax/swing/plaf/PopupMenuUI -javax/swing/plaf/basic/BasicLookAndFeel$AWTEventHelper -java/awt/event/AWTEventListenerProxy -java/awt/Toolkit$SelectiveAWTEventListener -java/awt/Toolkit$ToolkitEventMulticaster -javax/swing/plaf/basic/BasicLookAndFeel$1 -javax/swing/plaf/metal/MetalBorders$PopupMenuBorder -javax/swing/plaf/basic/BasicPopupMenuUI$BasicPopupMenuListener -javax/swing/event/PopupMenuListener -javax/swing/plaf/basic/BasicPopupMenuUI$BasicMenuKeyListener -javax/swing/plaf/basic/BasicPopupMenuUI$MouseGrabber -javax/swing/MenuSelectionManager -javax/swing/plaf/basic/BasicPopupMenuUI$MenuKeyboardHelper -javax/swing/plaf/basic/BasicPopupMenuUI$MenuKeyboardHelper$1 -java/awt/event/FocusAdapter -javax/swing/JMenu$WinListener -java/awt/event/WindowAdapter -javax/swing/JPopupMenu$Separator -javax/swing/JSeparator -javax/swing/plaf/metal/MetalPopupMenuSeparatorUI -javax/swing/plaf/metal/MetalSeparatorUI -javax/swing/plaf/basic/BasicSeparatorUI -javax/swing/plaf/SeparatorUI -javax/swing/JComboBox -javax/swing/event/ListDataListener -javax/swing/event/CaretEvent -javax/swing/text/TabExpander -javax/swing/JScrollBar -java/awt/Adjustable -javax/swing/event/MouseInputAdapter -javax/swing/JScrollBar$ModelListener -javax/swing/DefaultBoundedRangeModel -javax/swing/BoundedRangeModel -javax/swing/plaf/metal/MetalScrollBarUI -javax/swing/plaf/basic/BasicScrollBarUI -javax/swing/plaf/ScrollBarUI -javax/swing/plaf/metal/MetalBumps -javax/swing/plaf/metal/MetalScrollButton -javax/swing/plaf/basic/BasicArrowButton -javax/swing/plaf/basic/BasicScrollBarUI$TrackListener -javax/swing/plaf/basic/BasicScrollBarUI$ArrowButtonListener -javax/swing/plaf/basic/BasicScrollBarUI$ModelListener -javax/swing/plaf/metal/MetalScrollBarUI$ScrollBarListener -javax/swing/plaf/basic/BasicScrollBarUI$PropertyChangeHandler -javax/swing/plaf/basic/BasicScrollBarUI$Handler -javax/swing/plaf/basic/BasicScrollBarUI$ScrollListener -javax/swing/CellRendererPane -javax/swing/border/MatteBorder -sun/font/StandardGlyphVector -java/awt/font/GlyphVector -sun/font/StandardGlyphVector$GlyphStrike -sun/font/CoreMetrics -sun/font/FontLineMetrics -java/awt/font/LineMetrics -javax/swing/ComboBoxModel -javax/swing/ListModel -javax/swing/ListCellRenderer -javax/swing/DefaultComboBoxModel -javax/swing/MutableComboBoxModel -javax/swing/AbstractListModel -javax/swing/JComboBox$1 -javax/swing/AncestorNotifier -javax/swing/plaf/metal/MetalComboBoxUI -javax/swing/plaf/basic/BasicComboBoxUI -javax/swing/plaf/ComboBoxUI -javax/swing/plaf/metal/MetalComboBoxUI$MetalComboBoxLayoutManager -javax/swing/plaf/basic/BasicComboBoxUI$ComboBoxLayoutManager -javax/swing/plaf/basic/BasicComboPopup -javax/swing/plaf/basic/ComboPopup -javax/swing/plaf/basic/BasicComboPopup$EmptyListModelClass -javax/swing/border/LineBorder -javax/swing/plaf/basic/BasicComboPopup$1 -javax/swing/JList -javax/swing/DropMode -javax/swing/DefaultListSelectionModel -javax/swing/ListSelectionModel -javax/swing/plaf/basic/BasicListUI -javax/swing/plaf/ListUI -javax/swing/plaf/basic/BasicListUI$ListTransferHandler -javax/swing/TransferHandler -javax/swing/TransferHandler$TransferAction -javax/swing/DefaultListCellRenderer$UIResource -javax/swing/DefaultListCellRenderer -javax/swing/TransferHandler$SwingDropTarget -java/awt/dnd/DropTargetContext -java/awt/datatransfer/SystemFlavorMap -java/awt/datatransfer/FlavorMap -java/awt/datatransfer/FlavorTable -javax/swing/TransferHandler$DropHandler -javax/swing/TransferHandler$TransferSupport -javax/swing/plaf/basic/BasicListUI$Handler -javax/swing/event/ListSelectionListener -javax/swing/plaf/basic/DragRecognitionSupport$BeforeDrag -javax/swing/plaf/basic/BasicComboPopup$Handler -javax/swing/JScrollPane -javax/swing/ScrollPaneConstants -javax/swing/ScrollPaneLayout$UIResource -javax/swing/ScrollPaneLayout -javax/swing/JViewport -javax/swing/ViewportLayout -javax/swing/plaf/basic/BasicViewportUI -javax/swing/plaf/ViewportUI -javax/swing/JScrollPane$ScrollBar -javax/swing/JViewport$ViewListener -java/awt/event/ComponentAdapter -javax/swing/plaf/metal/MetalScrollPaneUI -javax/swing/plaf/basic/BasicScrollPaneUI -javax/swing/plaf/ScrollPaneUI -javax/swing/plaf/metal/MetalBorders$ScrollPaneBorder -javax/swing/plaf/basic/BasicScrollPaneUI$Handler -javax/swing/plaf/metal/MetalScrollPaneUI$1 -javax/swing/plaf/basic/BasicComboBoxRenderer$UIResource -javax/swing/plaf/basic/BasicComboBoxRenderer -javax/swing/plaf/metal/MetalComboBoxEditor$UIResource -javax/swing/plaf/metal/MetalComboBoxEditor -javax/swing/plaf/basic/BasicComboBoxEditor -javax/swing/ComboBoxEditor -javax/swing/plaf/basic/BasicComboBoxEditor$BorderlessTextField -javax/swing/JTextField$NotifyAction -javax/swing/text/TextAction -javax/swing/AbstractAction -javax/swing/text/JTextComponent$MutableCaretEvent -javax/swing/plaf/metal/MetalTextFieldUI -javax/swing/plaf/basic/BasicTextFieldUI -javax/swing/plaf/basic/BasicTextUI -javax/swing/text/ViewFactory -javax/swing/plaf/TextUI -javax/swing/plaf/basic/BasicTextUI$BasicCursor -javax/swing/text/DefaultEditorKit -javax/swing/text/EditorKit -javax/swing/text/DefaultEditorKit$InsertContentAction -javax/swing/text/DefaultEditorKit$DeletePrevCharAction -javax/swing/text/DefaultEditorKit$DeleteNextCharAction -javax/swing/text/DefaultEditorKit$ReadOnlyAction -javax/swing/text/DefaultEditorKit$DeleteWordAction -javax/swing/text/DefaultEditorKit$WritableAction -javax/swing/text/DefaultEditorKit$CutAction -javax/swing/text/DefaultEditorKit$CopyAction -javax/swing/text/DefaultEditorKit$PasteAction -javax/swing/text/DefaultEditorKit$VerticalPageAction -javax/swing/text/DefaultEditorKit$PageAction -javax/swing/text/DefaultEditorKit$InsertBreakAction -javax/swing/text/DefaultEditorKit$BeepAction -javax/swing/text/DefaultEditorKit$NextVisualPositionAction -javax/swing/text/DefaultEditorKit$BeginWordAction -javax/swing/text/DefaultEditorKit$EndWordAction -javax/swing/text/DefaultEditorKit$PreviousWordAction -javax/swing/text/DefaultEditorKit$NextWordAction -javax/swing/text/DefaultEditorKit$BeginLineAction -javax/swing/text/DefaultEditorKit$EndLineAction -javax/swing/text/DefaultEditorKit$BeginParagraphAction -javax/swing/text/DefaultEditorKit$EndParagraphAction -javax/swing/text/DefaultEditorKit$BeginAction -javax/swing/text/DefaultEditorKit$EndAction -javax/swing/text/DefaultEditorKit$DefaultKeyTypedAction -javax/swing/text/DefaultEditorKit$InsertTabAction -javax/swing/text/DefaultEditorKit$SelectWordAction -javax/swing/text/DefaultEditorKit$SelectLineAction -javax/swing/text/DefaultEditorKit$SelectParagraphAction -javax/swing/text/DefaultEditorKit$SelectAllAction -javax/swing/text/DefaultEditorKit$UnselectAction -javax/swing/text/DefaultEditorKit$ToggleComponentOrientationAction -javax/swing/text/DefaultEditorKit$DumpModelAction -javax/swing/plaf/basic/BasicTextUI$TextTransferHandler -javax/swing/text/Position$Bias -javax/swing/plaf/basic/BasicTextUI$RootView -javax/swing/text/View -javax/swing/plaf/basic/BasicTextUI$UpdateHandler -javax/swing/event/DocumentListener -javax/swing/plaf/basic/BasicTextUI$DragListener -javax/swing/plaf/basic/BasicComboBoxEditor$UIResource -javax/swing/plaf/basic/BasicTextUI$BasicCaret -javax/swing/text/DefaultCaret -javax/swing/text/Caret -javax/swing/text/DefaultCaret$Handler -java/awt/datatransfer/ClipboardOwner -javax/swing/plaf/basic/BasicTextUI$BasicHighlighter -javax/swing/text/DefaultHighlighter -javax/swing/text/LayeredHighlighter -javax/swing/text/Highlighter -javax/swing/text/Highlighter$Highlight -javax/swing/text/DefaultHighlighter$DefaultHighlightPainter -javax/swing/text/LayeredHighlighter$LayerPainter -javax/swing/text/Highlighter$HighlightPainter -javax/swing/text/DefaultHighlighter$SafeDamager -sun/swing/plaf/synth/SynthUI -javax/swing/plaf/synth/SynthConstants -javax/swing/text/FieldView -javax/swing/text/PlainView -javax/swing/text/JTextComponent$DefaultKeymap -javax/swing/text/Keymap -javax/swing/text/JTextComponent$KeymapWrapper -javax/swing/text/JTextComponent$KeymapActionMap -javax/swing/plaf/basic/BasicTextUI$FocusAction -javax/swing/plaf/basic/BasicTextUI$TextActionWrapper -javax/swing/JTextArea -javax/swing/JEditorPane -javax/swing/JTextField$ScrollRepainter -javax/swing/plaf/metal/MetalComboBoxEditor$1 -javax/swing/plaf/metal/MetalComboBoxEditor$EditorBorder -javax/swing/plaf/metal/MetalComboBoxUI$MetalPropertyChangeListener -javax/swing/plaf/basic/BasicComboBoxUI$PropertyChangeHandler -javax/swing/plaf/basic/BasicComboBoxUI$Handler -javax/swing/plaf/metal/MetalComboBoxButton -javax/swing/plaf/metal/MetalComboBoxIcon -javax/swing/plaf/metal/MetalComboBoxButton$1 -javax/swing/plaf/basic/BasicComboBoxUI$DefaultKeySelectionManager -javax/swing/JComboBox$KeySelectionManager -javax/swing/JToolBar$DefaultToolBarLayout -javax/swing/plaf/metal/MetalToolBarUI -javax/swing/plaf/basic/BasicToolBarUI -javax/swing/plaf/ToolBarUI -javax/swing/plaf/metal/MetalBorders$ToolBarBorder -javax/swing/plaf/metal/MetalLookAndFeel$MetalLazyValue$1 -javax/swing/plaf/metal/MetalBorders$RolloverButtonBorder -javax/swing/plaf/metal/MetalBorders$RolloverMarginBorder -javax/swing/plaf/basic/BasicBorders$RadioButtonBorder -javax/swing/plaf/basic/BasicBorders$ButtonBorder -javax/swing/plaf/basic/BasicBorders$RolloverMarginBorder -javax/swing/plaf/metal/MetalToolBarUI$MetalDockingListener -javax/swing/plaf/basic/BasicToolBarUI$DockingListener -javax/swing/plaf/basic/BasicToolBarUI$Handler -javax/swing/border/EtchedBorder -javax/swing/JToolBar$Separator -javax/swing/plaf/basic/BasicToolBarSeparatorUI -sun/font/FontDesignMetrics$MetricsKey -java/applet/Applet -java/awt/Panel -javax/swing/KeyboardManager$ComponentKeyStrokePair -sun/awt/im/InputMethodContext -java/awt/im/spi/InputMethodContext -sun/awt/im/InputContext -sun/awt/windows/WInputMethod -sun/awt/im/InputMethodAdapter -java/awt/im/spi/InputMethod -java/util/Collections$UnmodifiableMap -javax/swing/SizeRequirements -javax/swing/plaf/basic/BasicGraphicsUtils -java/awt/event/AdjustmentEvent -java/awt/MenuBar -java/awt/LightweightDispatcher$2 -java/io/StringWriter -java/io/UnsupportedEncodingException -java/lang/StringCoding$StringEncoder -java/net/UnknownHostException -java/net/Socket -java/nio/channels/SocketChannel -java/nio/channels/spi/AbstractSelectableChannel -java/nio/channels/SelectableChannel -java/net/SocketException -java/net/SocketImplFactory -java/net/Proxy -java/net/SocksSocketImpl$5 -java/net/ProxySelector -sun/net/spi/DefaultProxySelector -sun/net/spi/DefaultProxySelector$1 -sun/net/NetProperties -sun/net/NetProperties$1 -sun/net/spi/DefaultProxySelector$NonProxyInfo -java/util/regex/ASCII -java/util/regex/Pattern$GroupCurly -java/net/Inet6Address -java/net/URI -java/net/URI$Parser -java/net/Proxy$Type -java/net/SocketTimeoutException -java/io/InterruptedIOException -javax/swing/UnsupportedLookAndFeelException -java/lang/UnsatisfiedLinkError -javax/swing/Box$Filler -javax/swing/JComponent$2 -sun/net/ProgressMonitor -sun/net/DefaultProgressMeteringPolicy -sun/net/ProgressMeteringPolicy -sun/net/www/MimeTable -java/net/FileNameMap -sun/net/www/MimeTable$1 -sun/net/www/MimeTable$2 -sun/net/www/MimeEntry -java/net/URLConnection$1 -java/text/SimpleDateFormat -java/text/DateFormat -java/text/DateFormat$Field -java/util/Calendar -java/util/GregorianCalendar -sun/util/resources/CalendarData -sun/util/resources/LocaleNamesBundle -sun/util/resources/CalendarData_en -java/text/DateFormatSymbols -java/text/spi/DateFormatSymbolsProvider -sun/text/resources/FormatData -sun/text/resources/FormatData_en -sun/text/resources/FormatData_en_US -java/text/NumberFormat -java/text/spi/NumberFormatProvider -java/text/DecimalFormatSymbols -java/text/spi/DecimalFormatSymbolsProvider -java/util/Currency -java/util/Currency$1 -java/util/CurrencyData -java/util/spi/CurrencyNameProvider -sun/util/resources/CurrencyNames -sun/util/resources/CurrencyNames_en_US -java/text/DecimalFormat -java/text/DigitList -java/math/RoundingMode -java/text/DontCareFieldPosition -java/text/DontCareFieldPosition$1 -java/text/Format$FieldDelegate -javax/swing/plaf/BorderUIResource -javax/swing/BorderFactory -javax/swing/border/BevelBorder -javax/swing/plaf/metal/MetalIconFactory$TreeFolderIcon -javax/swing/plaf/metal/MetalIconFactory$FolderIcon16 -java/awt/TrayIcon -java/awt/EventDispatchThread$StopDispatchEvent -java/util/zip/ZipInputStream -java/io/PushbackInputStream -java/util/zip/CRC32 -java/util/zip/Checksum -java/lang/Thread$State -javax/swing/SwingUtilities$SharedOwnerFrame -javax/swing/JTable -javax/swing/event/TableModelListener -javax/swing/event/TableColumnModelListener -javax/swing/event/CellEditorListener -javax/swing/event/RowSorterListener -com/sun/awt/AWTUtilities$Translucency -com/sun/awt/AWTUtilities$1 -java/lang/ClassFormatError -java/awt/GraphicsCallback$PaintCallback -sun/awt/SunGraphicsCallback -java/awt/Component$ProxyCapabilities -sun/java2d/pipe/hw/ExtendedBufferCapabilities -java/awt/BufferCapabilities$FlipContents -java/awt/AttributeValue -sun/awt/SubRegionShowable -sun/print/PrinterGraphicsConfig -sun/java2d/opengl/WGLGraphicsConfig -sun/java2d/opengl/OGLGraphicsConfig -javax/swing/JTabbedPane -javax/swing/JTabbedPane$ModelListener -javax/swing/plaf/metal/MetalTabbedPaneUI -javax/swing/plaf/basic/BasicTabbedPaneUI -javax/swing/plaf/TabbedPaneUI -javax/swing/plaf/metal/MetalTabbedPaneUI$TabbedPaneLayout -javax/swing/plaf/basic/BasicTabbedPaneUI$TabbedPaneLayout -javax/swing/plaf/basic/BasicTabbedPaneUI$TabbedPaneScrollLayout -javax/swing/plaf/basic/BasicTabbedPaneUI$Handler -sun/swing/ImageIconUIResource -javax/swing/GrayFilter -java/awt/image/RGBImageFilter -java/awt/image/ImageFilter -java/awt/image/FilteredImageSource -org/w3c/dom/Node -org/xml/sax/SAXException -javax/xml/parsers/ParserConfigurationException -org/xml/sax/EntityResolver -java/security/NoSuchAlgorithmException -java/security/GeneralSecurityException -java/util/zip/GZIPInputStream -java/util/zip/DeflaterOutputStream -org/xml/sax/InputSource -javax/xml/parsers/DocumentBuilderFactory -javax/xml/parsers/FactoryFinder -javax/xml/parsers/SecuritySupport -javax/xml/parsers/SecuritySupport$2 -javax/xml/parsers/SecuritySupport$5 -javax/xml/parsers/SecuritySupport$1 -javax/xml/parsers/SecuritySupport$4 -javax/xml/parsers/DocumentBuilder -org/xml/sax/helpers/DefaultHandler -org/xml/sax/DTDHandler -org/xml/sax/ContentHandler -org/xml/sax/ErrorHandler -org/w3c/dom/Document -org/xml/sax/SAXNotSupportedException -org/xml/sax/Locator -org/xml/sax/SAXNotRecognizedException -org/xml/sax/SAXParseException -org/w3c/dom/NodeList -org/w3c/dom/events/EventTarget -org/w3c/dom/traversal/DocumentTraversal -org/w3c/dom/events/DocumentEvent -org/w3c/dom/ranges/DocumentRange -org/w3c/dom/Entity -org/w3c/dom/Element -org/w3c/dom/CharacterData -org/w3c/dom/CDATASection -org/w3c/dom/Text -org/xml/sax/AttributeList -org/w3c/dom/DOMException -org/w3c/dom/Notation -org/w3c/dom/DocumentType -org/w3c/dom/Attr -org/w3c/dom/EntityReference -org/w3c/dom/ProcessingInstruction -org/w3c/dom/DocumentFragment -org/w3c/dom/Comment -org/w3c/dom/events/Event -org/w3c/dom/events/MutationEvent -org/w3c/dom/traversal/TreeWalker -org/w3c/dom/ranges/Range -org/w3c/dom/traversal/NodeIterator -org/w3c/dom/events/EventException -org/w3c/dom/NamedNodeMap -java/lang/StringIndexOutOfBoundsException -java/awt/GridLayout -javax/swing/plaf/metal/MetalRadioButtonUI -javax/swing/plaf/basic/BasicRadioButtonUI -javax/swing/plaf/basic/BasicBorders -javax/swing/plaf/metal/MetalIconFactory$RadioButtonIcon -java/awt/event/ItemEvent -java/awt/CardLayout$Card -javax/swing/JCheckBox -javax/swing/event/ListSelectionEvent -javax/swing/plaf/metal/MetalCheckBoxUI -javax/swing/plaf/metal/MetalIconFactory$CheckBoxIcon -java/lang/ExceptionInInitializerError -com/sun/java/swing/plaf/windows/WindowsTabbedPaneUI -javax/swing/JProgressBar -javax/swing/JProgressBar$ModelListener -javax/swing/plaf/metal/MetalProgressBarUI -javax/swing/plaf/basic/BasicProgressBarUI -javax/swing/plaf/ProgressBarUI -javax/swing/plaf/BorderUIResource$LineBorderUIResource -javax/swing/plaf/basic/BasicProgressBarUI$Handler -javax/swing/tree/TreeModel -javax/swing/table/TableCellRenderer -javax/swing/table/JTableHeader -javax/swing/event/TreeExpansionListener -javax/swing/table/AbstractTableModel -javax/swing/table/TableModel -javax/swing/table/DefaultTableCellRenderer -javax/swing/JTree -javax/swing/tree/TreeSelectionModel -javax/swing/tree/DefaultTreeCellRenderer -javax/swing/tree/TreeCellRenderer -javax/swing/table/TableCellEditor -javax/swing/CellEditor -javax/swing/JToolTip -javax/swing/table/TableColumn -javax/swing/table/DefaultTableColumnModel -javax/swing/table/TableColumnModel -javax/swing/table/DefaultTableModel -javax/swing/event/TableModelEvent -sun/swing/table/DefaultTableCellHeaderRenderer -sun/swing/table/DefaultTableCellHeaderRenderer$EmptyIcon -javax/swing/plaf/basic/BasicTableHeaderUI -javax/swing/plaf/TableHeaderUI -javax/swing/plaf/basic/BasicTableHeaderUI$1 -javax/swing/plaf/basic/BasicTableHeaderUI$MouseInputHandler -javax/swing/DefaultCellEditor -javax/swing/tree/TreeCellEditor -javax/swing/AbstractCellEditor -javax/swing/plaf/basic/BasicTableUI -javax/swing/plaf/TableUI -javax/swing/plaf/basic/BasicTableUI$TableTransferHandler -javax/swing/plaf/basic/BasicTableUI$Handler -javax/swing/tree/DefaultTreeSelectionModel -javax/swing/tree/TreePath -javax/swing/plaf/metal/MetalTreeUI -javax/swing/plaf/basic/BasicTreeUI -javax/swing/plaf/TreeUI -javax/swing/plaf/basic/BasicTreeUI$Actions -javax/swing/plaf/basic/BasicTreeUI$TreeTransferHandler -javax/swing/plaf/metal/MetalTreeUI$LineListener -javax/swing/plaf/basic/BasicTreeUI$Handler -javax/swing/event/TreeModelListener -javax/swing/event/TreeSelectionListener -javax/swing/tree/VariableHeightLayoutCache -javax/swing/tree/AbstractLayoutCache -javax/swing/tree/RowMapper -javax/swing/plaf/basic/BasicTreeUI$NodeDimensionsHandler -javax/swing/tree/AbstractLayoutCache$NodeDimensions -javax/swing/JTree$TreeModelHandler -javax/swing/tree/VariableHeightLayoutCache$TreeStateNode -javax/swing/tree/DefaultMutableTreeNode -javax/swing/tree/MutableTreeNode -javax/swing/tree/DefaultMutableTreeNode$1 -javax/swing/tree/DefaultMutableTreeNode$PreorderEnumeration -javax/swing/event/TableColumnModelEvent -java/text/ParseException -java/text/NumberFormat$Field -javax/swing/event/UndoableEditListener -javax/swing/filechooser/FileFilter -javax/swing/tree/DefaultTreeModel -javax/swing/tree/DefaultTreeCellEditor -javax/swing/tree/DefaultTreeCellEditor$1 -javax/swing/tree/DefaultTreeCellEditor$DefaultTextField -javax/swing/DefaultCellEditor$1 -javax/swing/DefaultCellEditor$EditorDelegate -javax/swing/tree/DefaultTreeCellEditor$EditorContainer -javax/swing/JTree$TreeSelectionRedirector -javax/swing/event/TreeModelEvent -javax/swing/plaf/metal/MetalSplitPaneUI -javax/swing/plaf/basic/BasicSplitPaneUI -javax/swing/plaf/SplitPaneUI -javax/swing/plaf/basic/BasicSplitPaneDivider -javax/swing/plaf/basic/BasicBorders$SplitPaneBorder -javax/swing/plaf/metal/MetalSplitPaneDivider -javax/swing/plaf/basic/BasicSplitPaneDivider$DividerLayout -javax/swing/plaf/basic/BasicSplitPaneDivider$MouseHandler -javax/swing/plaf/basic/BasicBorders$SplitPaneDividerBorder -javax/swing/plaf/basic/BasicSplitPaneUI$BasicHorizontalLayoutManager -javax/swing/plaf/basic/BasicSplitPaneUI$1 -javax/swing/plaf/basic/BasicSplitPaneUI$Handler -javax/swing/plaf/metal/MetalSplitPaneDivider$1 -javax/swing/plaf/basic/BasicSplitPaneDivider$OneTouchActionHandler -javax/swing/plaf/metal/MetalSplitPaneDivider$2 -javax/swing/border/TitledBorder -javax/swing/plaf/basic/BasicTextAreaUI -java/util/Collections$UnmodifiableCollection$1 -java/net/NoRouteToHostException -java/net/BindException -javax/swing/tree/PathPlaceHolder -javax/swing/event/TreeSelectionEvent -javax/swing/JList$3 -javax/swing/JList$ListSelectionHandler -javax/swing/JSlider -javax/swing/JSlider$ModelListener -javax/swing/plaf/metal/MetalSliderUI -javax/swing/plaf/basic/BasicSliderUI -javax/swing/plaf/SliderUI -javax/swing/plaf/basic/BasicSliderUI$Actions -javax/swing/plaf/metal/MetalIconFactory$HorizontalSliderThumbIcon -javax/swing/plaf/metal/MetalIconFactory$VerticalSliderThumbIcon -javax/swing/plaf/basic/BasicSliderUI$TrackListener -javax/swing/plaf/basic/BasicSliderUI$Handler -javax/swing/plaf/basic/BasicSliderUI$ScrollListener -javax/swing/plaf/metal/MetalSliderUI$MetalPropertyListener -javax/swing/plaf/basic/BasicSliderUI$PropertyChangeHandler -sun/java2d/HeadlessGraphicsEnvironment -java/util/Hashtable$KeySet -sun/font/FontManager$2 -sun/java2d/SunGraphicsEnvironment$3 -sun/java2d/SunGraphicsEnvironment$4 -javax/swing/DefaultListModel -javax/swing/event/ListDataEvent -javax/sound/sampled/DataLine -javax/sound/sampled/Line -javax/sound/sampled/Line$Info -javax/sound/sampled/DataLine$Info -javax/sound/sampled/Control$Type -javax/sound/sampled/FloatControl$Type -javax/sound/sampled/LineUnavailableException -javax/sound/sampled/UnsupportedAudioFileException -javax/swing/JRadioButtonMenuItem -javax/swing/JMenuItem$AccessibleJMenuItem -javax/swing/AbstractButton$AccessibleAbstractButton -javax/accessibility/AccessibleAction -javax/accessibility/AccessibleValue -javax/accessibility/AccessibleText -javax/accessibility/AccessibleExtendedComponent -javax/accessibility/AccessibleComponent -javax/swing/JComponent$AccessibleJComponent -java/awt/Container$AccessibleAWTContainer -java/awt/Component$AccessibleAWTComponent -javax/accessibility/AccessibleRelationSet -javax/accessibility/AccessibleState -javax/accessibility/AccessibleBundle -javax/swing/plaf/basic/BasicCheckBoxMenuItemUI -javax/swing/plaf/metal/MetalIconFactory$CheckBoxMenuItemIcon -javax/swing/JCheckBoxMenuItem$AccessibleJCheckBoxMenuItem -javax/swing/plaf/basic/BasicRadioButtonMenuItemUI -javax/swing/plaf/metal/MetalIconFactory$RadioButtonMenuItemIcon -sun/awt/image/ImageDecoder$1 -javax/swing/JTabbedPane$Page -java/net/DatagramSocket -java/net/MulticastSocket -java/net/DatagramPacket -sun/net/InetAddressCachePolicy$1 -sun/security/action/GetIntegerAction -sun/net/InetAddressCachePolicy$2 -java/net/InetAddress$CacheEntry -java/net/PlainDatagramSocketImpl -java/text/Collator -java/text/spi/CollatorProvider -sun/text/resources/CollationData -sun/text/resources/CollationData_en -sun/util/EmptyListResourceBundle -java/text/RuleBasedCollator -java/text/CollationRules -java/text/RBCollationTables -java/text/RBTableBuilder -java/text/RBCollationTables$BuildAPI -sun/text/IntHashtable -sun/text/UCompactIntArray -sun/text/normalizer/NormalizerImpl -sun/text/normalizer/ICUData -sun/text/normalizer/NormalizerDataReader -sun/text/normalizer/ICUBinary$Authenticate -sun/text/normalizer/ICUBinary -sun/text/normalizer/NormalizerImpl$FCDTrieImpl -sun/text/normalizer/Trie$DataManipulate -sun/text/normalizer/NormalizerImpl$NormTrieImpl -sun/text/normalizer/NormalizerImpl$AuxTrieImpl -sun/text/normalizer/IntTrie -sun/text/normalizer/Trie -sun/text/normalizer/CharTrie -sun/text/normalizer/CharTrie$FriendAgent -sun/text/normalizer/UnicodeSet -sun/text/normalizer/UnicodeMatcher -sun/text/normalizer/NormalizerImpl$DecomposeArgs -java/text/MergeCollation -java/text/PatternEntry$Parser -java/text/PatternEntry -java/text/EntryPair -sun/text/ComposedCharIter -sun/text/normalizer/UTF16 -sun/net/www/protocol/http/Handler -java/security/SignatureException -java/security/InvalidKeyException -java/security/KeyException -java/security/Signature -java/io/ObjectInputStream$BlockDataInputStream -java/io/ObjectInputStream$HandleTable -java/io/ObjectInputStream$HandleTable$HandleList -java/io/ObjectInputStream$ValidationList -java/io/Bits -java/io/ObjectStreamClass$Caches -sun/security/provider/DSAPublicKey -java/io/ObjectStreamClass$WeakClassKey -java/security/interfaces/DSAKey -java/security/PublicKey -java/io/ObjectStreamClass$EntryFuture -java/security/Key -sun/reflect/SerializationConstructorAccessorImpl -java/io/ObjectStreamClass$FieldReflectorKey -java/io/ObjectStreamClass$FieldReflector -sun/security/util/DerEncoder -java/io/ObjectStreamClass$3 -java/io/ObjectStreamClass$4 -java/io/ObjectStreamClass$5 -java/security/MessageDigest -sun/security/jca/GetInstance -java/math/BigInteger -java/io/ObjectStreamClass$ClassDataSlot -sun/security/jca/ProviderConfig -sun/security/util/DerInputStream -sun/security/jca/ProviderList$3 -sun/security/util/DerInputBuffer -sun/security/jca/ProviderList$1 -java/security/AlgorithmParameters -java/security/AlgorithmParametersSpi -sun/security/jca/ProviderList$2 -sun/security/jca/ProviderConfig$1 -sun/security/jca/ProviderConfig$3 -java/security/Provider$Service -java/security/Provider$UString -sun/security/provider/DSAParameters -sun/security/jca/GetInstance$Instance -sun/security/util/ByteArrayLexOrder -sun/security/util/ByteArrayTagOrder -sun/security/provider/DigestBase -java/security/MessageDigest$Delegate -sun/security/util/DerIndefLenConverter -java/io/InvalidClassException -java/io/ObjectOutputStream$BlockDataOutputStream -java/io/ObjectInputStream$GetFieldImpl -java/io/ObjectOutputStream$ReplaceTable -sun/security/jca/ServiceId -sun/security/jca/ProviderList$ServiceList -sun/security/jca/ProviderList$ServiceList$1 -java/security/Signature$Delegate -java/security/interfaces/DSAPrivateKey -java/security/PrivateKey -sun/security/provider/DSA$SHA1withDSA -sun/security/provider/DSA -java/security/spec/DSAParameterSpec -java/security/spec/AlgorithmParameterSpec -java/math/MutableBigInteger -java/math/SignedMutableBigInteger -java/awt/Window$1DisposeAction -java/awt/EventQueue$1AWTInvocationLock -javax/swing/SystemEventQueueUtilities$RunnableCanvas -javax/swing/SystemEventQueueUtilities$RunnableCanvasGraphics -sun/awt/image/VSyncedBSManager -javax/swing/JTable$2 -javax/swing/JTable$Resizable3 -javax/swing/JTable$Resizable2 -javax/swing/JTable$5 -java/awt/DefaultKeyboardFocusManager$DefaultKeyboardFocusManagerSentEvent -sun/nio/cs/UTF_16LE -com/sun/java/swing/plaf/windows/WindowsLookAndFeel -com/sun/java/swing/plaf/windows/XPStyle -com/sun/java/swing/plaf/windows/XPStyle$SkinPainter -sun/swing/CachedPainter -com/sun/java/swing/plaf/windows/WindowsRootPaneUI -com/sun/java/swing/plaf/windows/WindowsRootPaneUI$AltProcessor -java/awt/SystemColor -com/sun/java/swing/plaf/windows/WindowsTreeUI$ExpandedIcon -com/sun/java/swing/plaf/windows/WindowsTreeUI$CollapsedIcon -com/sun/java/swing/plaf/windows/DesktopProperty -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$XPColorValue -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$XPValue -com/sun/java/swing/plaf/windows/TMSchema$Part -com/sun/java/swing/plaf/windows/TMSchema$Control -com/sun/java/swing/plaf/windows/TMSchema$Prop -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$XPColorValue$XPColorValueKey -com/sun/java/swing/plaf/windows/XPStyle$Skin -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$WindowsFontProperty -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$FontDesktopProperty -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$TriggerDesktopProperty -com/sun/java/swing/plaf/windows/DesktopProperty$WeakPCL -com/sun/java/swing/plaf/windows/WindowsClassicLookAndFeel -com/sun/java/swing/plaf/windows/TMSchema$State -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$LazyWindowsIcon -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$XPBorderValue -com/sun/java/swing/plaf/windows/WindowsIconFactory -com/sun/java/swing/plaf/windows/WindowsIconFactory$FrameButtonIcon -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$XPDLUValue -com/sun/java/swing/plaf/windows/WindowsLookAndFeel$ActiveWindowsIcon -sun/swing/SwingUtilities2$2$1 -sun/awt/image/ByteArrayImageSource -com/sun/java/swing/plaf/windows/resources/windows -com/sun/java/swing/plaf/windows/WindowsLabelUI -sun/swing/ImageCache -com/sun/java/swing/plaf/windows/WindowsButtonUI -com/sun/java/swing/plaf/windows/WindowsToggleButtonUI -javax/swing/plaf/basic/BasicBorders$FieldBorder -com/sun/java/swing/plaf/windows/WindowsMenuBarUI -javax/swing/plaf/basic/BasicBorders$MenuBarBorder -com/sun/java/swing/plaf/windows/WindowsMenuBarUI$TakeFocus -javax/swing/plaf/basic/BasicMenuBarUI$Actions -com/sun/java/swing/plaf/windows/WindowsMenuUI -com/sun/java/swing/plaf/windows/WindowsMenuUI$1 -com/sun/java/swing/plaf/windows/WindowsMenuItemUIAccessor -com/sun/java/swing/plaf/windows/WindowsIconFactory$MenuArrowIcon -javax/swing/plaf/basic/BasicIconFactory -javax/swing/plaf/basic/BasicIconFactory$MenuItemCheckIcon -com/sun/java/swing/plaf/windows/WindowsMenuUI$WindowsMouseInputHandler -javax/swing/plaf/basic/BasicMenuUI$MouseInputHandler -com/sun/java/swing/plaf/windows/WindowsMenuItemUI -com/sun/java/swing/plaf/windows/WindowsMenuItemUI$1 -com/sun/java/swing/plaf/windows/WindowsIconFactory$MenuItemArrowIcon -com/sun/java/swing/plaf/windows/WindowsIconFactory$MenuItemCheckIcon -com/sun/java/swing/plaf/windows/WindowsPopupMenuUI -javax/swing/Popup -com/sun/java/swing/plaf/windows/WindowsPopupMenuUI$MnemonicListener -com/sun/java/swing/plaf/windows/WindowsPopupMenuSeparatorUI -javax/swing/plaf/basic/BasicPopupMenuSeparatorUI -com/sun/java/swing/plaf/windows/WindowsScrollBarUI -com/sun/java/swing/plaf/windows/WindowsScrollBarUI$Grid -com/sun/java/swing/plaf/windows/WindowsScrollBarUI$WindowsArrowButton -com/sun/java/swing/plaf/windows/WindowsComboBoxUI -com/sun/java/swing/plaf/windows/WindowsComboBoxUI$1 -com/sun/java/swing/plaf/windows/WindowsComboBoxUI$2 -com/sun/java/swing/plaf/windows/WindowsComboBoxUI$WindowsComboBoxEditor -com/sun/java/swing/plaf/windows/WindowsTextFieldUI -com/sun/java/swing/plaf/windows/WindowsTextFieldUI$WindowsFieldCaret -com/sun/java/swing/plaf/windows/WindowsComboBoxUI$3 -com/sun/java/swing/plaf/windows/WindowsToolBarUI -com/sun/java/swing/plaf/windows/WindowsBorders -com/sun/java/swing/plaf/windows/WindowsBorders$ToolBarBorder -javax/swing/plaf/basic/BasicBorders$RolloverButtonBorder -com/sun/java/swing/plaf/windows/WindowsToolBarSeparatorUI -javax/swing/JRadioButton -java/awt/GraphicsCallback -java/awt/Component$FlipSubRegionBufferStrategy -java/awt/Component$FlipBufferStrategy -sun/java2d/d3d/D3DVolatileSurfaceManager -java/awt/print/PrinterGraphics -java/awt/PrintGraphics -sun/net/InetAddressCachePolicy -java/net/DatagramSocketImpl -java/security/SignatureSpi -java/io/ObjectInputStream$PeekInputStream -java/security/interfaces/DSAPublicKey -java/io/Externalizable -java/io/ObjectStreamClass$1 -java/io/DataOutputStream -java/io/ObjectStreamClass$MemberSignature -java/security/interfaces/DSAParams -java/security/MessageDigestSpi -sun/security/jca/ProviderList -sun/security/util/ObjectIdentifier -java/io/ObjectStreamException -java/io/ObjectInputStream$GetField -java/io/ObjectOutputStream$HandleTable -javax/swing/event/AncestorEvent -sun/java2d/SurfaceManagerFactory$1 -java/awt/Component$BltSubRegionBufferStrategy -java/awt/Component$BltBufferStrategy -sun/awt/image/BufferedImageGraphicsConfig -sun/awt/image/BufImgVolatileSurfaceManager -java/io/ObjectStreamClass$2 -sun/security/x509/X509Key -sun/security/util/DerOutputStream -sun/security/util/DerValue -java/io/ObjectInputStream$CallbackContext -javax/swing/BufferStrategyPaintManager$BufferInfo -sun/reflect/UnsafeQualifiedStaticLongFieldAccessorImpl -sun/security/jca/Providers -sun/security/provider/ByteArrayAccess -sun/applet/Main -sun/applet/AppletMessageHandler -sun/applet/resources/MsgAppletViewer -sun/applet/AppletSecurity -sun/awt/AWTSecurityManager -java/lang/SecurityManager -java/security/DomainCombiner -sun/applet/AppletSecurity$1 -java/lang/SecurityManager$1 -java/security/SecurityPermission -java/util/PropertyPermission -sun/applet/AppletViewer -java/applet/AppletContext -java/awt/print/Printable -java/util/logging/LogManager$6 -sun/applet/StdAppletViewerFactory -sun/applet/AppletViewerFactory -sun/security/util/SecurityConstants -java/awt/AWTPermission -java/net/NetPermission -java/net/SocketPermission -javax/security/auth/AuthPermission -sun/applet/AppletViewer$UserActionListener -sun/applet/AppletViewerPanel -sun/applet/AppletPanel -java/applet/AppletStub -sun/misc/MessageUtils -sun/applet/AppletPanel$10 -java/security/Policy$1 -java/util/concurrent/atomic/AtomicReference -sun/security/provider/PolicyFile$PolicyInfo -java/util/Collections$SynchronizedRandomAccessList -java/util/Collections$SynchronizedList -sun/security/provider/PolicyFile$4 -sun/security/provider/PolicyFile$PolicyEntry -sun/security/provider/PolicyParser -sun/security/provider/PolicyFile$1 -sun/security/provider/PolicyFile$3 -sun/security/util/PropertyExpander -sun/security/util/PolicyUtil -java/io/StreamTokenizer -sun/security/provider/PolicyParser$GrantEntry -sun/security/provider/PolicyParser$PermissionEntry -sun/security/provider/PolicyParser$ParsingException -sun/security/provider/PolicyFile$7 -sun/security/provider/PolicyFile$8 -sun/security/provider/PolicyFile$SelfPermission -java/net/SocketPermissionCollection -java/util/PropertyPermissionCollection -sun/applet/AppletPanel$9 -sun/applet/AppletClassLoader -sun/applet/AppletClassLoader$4 -sun/applet/AppletThreadGroup -sun/applet/AppContextCreator -java/lang/Thread$1 -sun/applet/AppletPanel$1 -sun/awt/AppContext$3 -sun/awt/MostRecentThreadAppContext -sun/awt/windows/WMenuBarPeer -java/awt/peer/MenuBarPeer -java/awt/peer/MenuComponentPeer -sun/awt/windows/WMenuPeer -java/awt/peer/MenuPeer -java/awt/peer/MenuItemPeer -sun/awt/windows/WMenuItemPeer -sun/awt/windows/WMenuItemPeer$2 -sun/awt/windows/awtLocalization -sun/awt/windows/WFontMetrics -sun/applet/AppletViewer$1 -sun/applet/AppletViewer$1AppletEventListener -sun/applet/AppletListener -sun/awt/CausedFocusEvent -sun/applet/AppletEvent -java/util/concurrent/locks/LockSupport -java/net/URLClassLoader$4 -sun/applet/AppletClassLoader$2 -javax/swing/JApplet -java/lang/ClassLoader$1 -sun/security/provider/PolicyFile$6 -java/security/PermissionsEnumerator -java/util/Collections$1 -sun/applet/AppletPanel$11 -javax/swing/SwingHeavyWeight -sun/applet/AppletPanel$8 -sun/applet/AppletPanel$2 -sun/applet/AppletPanel$3 -sun/applet/AppletPanel$6 -java/beans/PropertyVetoException -javax/swing/BufferStrategyPaintManager$1 -java/lang/ApplicationShutdownHooks$1 -sun/nio/cs/ISO_8859_1$Decoder -sun/rmi/transport/proxy/RMIHttpToCGISocketFactory -sun/rmi/runtime/Log$LoggerLog -sun/rmi/transport/proxy/RMIMasterSocketFactory -java/rmi/server/LogStream -sun/rmi/runtime/Log$LoggerLog$1 -sun/nio/ch/FileChannelImpl$FileLockTable -sun/nio/ch/FileChannelImpl$FileLockReference -java/lang/ProcessEnvironment$EntryComparator -java/lang/StringValue -java/rmi/server/RMIServerSocketFactory -java/lang/ProcessEnvironment -java/lang/ProcessEnvironment$CheckedEntrySet$1 -sun/rmi/runtime/Log$LogFactory -sun/nio/ch/FileKey -sun/rmi/transport/proxy/RMIHttpToPortSocketFactory -sun/rmi/runtime/Log -java/nio/DirectIntBufferU -java/util/logging/LogManager$3 -sun/nio/cs/ISO_8859_1$Encoder -java/rmi/server/RMIClientSocketFactory -java/util/logging/Formatter -java/lang/ProcessEnvironment$CheckedEntry -sun/nio/ch/FileChannelImpl$SharedFileLockTable -sun/rmi/transport/proxy/RMIDirectSocketFactory -sun/security/action/GetLongAction -sun/rmi/runtime/Log$InternalStreamHandler -java/lang/AssertionStatusDirectives -java/lang/Process -sun/nio/cs/ISO_8859_1 -java/nio/DoubleBuffer -java/nio/channels/FileLock -java/util/logging/SimpleFormatter -java/util/TreeMap$Entry -java/lang/ProcessBuilder -sun/net/util/URLUtil -sun/rmi/runtime/Log$LoggerLogFactory -java/util/logging/StreamHandler -java/nio/FloatBuffer -java/lang/ProcessImpl -sun/nio/ch/FileChannelImpl$1 -java/lang/ProcessImpl$1 -java/lang/ProcessEnvironment$CheckedEntrySet -sun/nio/ch/FileLockImpl -java/lang/ProcessEnvironment$NameComparator -sun/nio/ch/FileChannelImpl$FileLockTable$Releaser -java/net/PasswordAuthentication -java/rmi/server/RMISocketFactory -java/util/logging/ErrorManager -# abce0ad3b4fa88bb diff --git a/windows-dependencies/Java/lib/cmm/CIEXYZ.pf b/windows-dependencies/Java/lib/cmm/CIEXYZ.pf deleted file mode 100644 index db3ba20a9..000000000 Binary files a/windows-dependencies/Java/lib/cmm/CIEXYZ.pf and /dev/null differ diff --git a/windows-dependencies/Java/lib/cmm/GRAY.pf b/windows-dependencies/Java/lib/cmm/GRAY.pf deleted file mode 100644 index e31a4a777..000000000 Binary files a/windows-dependencies/Java/lib/cmm/GRAY.pf and /dev/null differ diff --git a/windows-dependencies/Java/lib/cmm/LINEAR_RGB.pf b/windows-dependencies/Java/lib/cmm/LINEAR_RGB.pf deleted file mode 100644 index eadae048d..000000000 Binary files a/windows-dependencies/Java/lib/cmm/LINEAR_RGB.pf and /dev/null differ diff --git a/windows-dependencies/Java/lib/cmm/PYCC.pf b/windows-dependencies/Java/lib/cmm/PYCC.pf deleted file mode 100644 index 1c49e0bb9..000000000 Binary files a/windows-dependencies/Java/lib/cmm/PYCC.pf and /dev/null differ diff --git a/windows-dependencies/Java/lib/cmm/sRGB.pf b/windows-dependencies/Java/lib/cmm/sRGB.pf deleted file mode 100644 index 7f9d18d09..000000000 Binary files a/windows-dependencies/Java/lib/cmm/sRGB.pf and /dev/null differ diff --git a/windows-dependencies/Java/lib/content-types.properties b/windows-dependencies/Java/lib/content-types.properties deleted file mode 100644 index 83ef8f5e5..000000000 --- a/windows-dependencies/Java/lib/content-types.properties +++ /dev/null @@ -1,272 +0,0 @@ -#sun.net.www MIME content-types table; version %I%, %G% -# -# Property fields: -# -# ::= 'description' '=' -# ::= 'file_extensions' '=' -# ::= 'icon' '=' -# ::= 'browser' | 'application' | 'save' | 'unknown' -# ::= 'application' '=' -# - -# -# The "we don't know anything about this data" type(s). -# Used internally to mark unrecognized types. -# -content/unknown: description=Unknown Content -unknown/unknown: description=Unknown Data Type - -# -# The template we should use for temporary files when launching an application -# to view a document of given type. -# -temp.file.template: c:\\temp\\%s - -# -# The "real" types. -# -application/octet-stream: \ - description=Generic Binary Stream;\ - file_extensions=.saveme,.dump,.hqx,.arc,.obj,.lib,.bin,.exe,.zip,.gz - -application/oda: \ - description=ODA Document;\ - file_extensions=.oda - -application/pdf: \ - description=Adobe PDF Format;\ - file_extensions=.pdf - -application/postscript: \ - description=Postscript File;\ - file_extensions=.eps,.ai,.ps;\ - icon=ps - -application/rtf: \ - description=Wordpad Document;\ - file_extensions=.rtf;\ - action=application;\ - application=wordpad.exe %s - -application/x-dvi: \ - description=TeX DVI File;\ - file_extensions=.dvi - -application/x-hdf: \ - description=Hierarchical Data Format;\ - file_extensions=.hdf;\ - action=save - -application/x-latex: \ - description=LaTeX Source;\ - file_extensions=.latex - -application/x-netcdf: \ - description=Unidata netCDF Data Format;\ - file_extensions=.nc,.cdf;\ - action=save - -application/x-tex: \ - description=TeX Source;\ - file_extensions=.tex - -application/x-texinfo: \ - description=Gnu Texinfo;\ - file_extensions=.texinfo,.texi - -application/x-troff: \ - description=Troff Source;\ - file_extensions=.t,.tr,.roff - -application/x-troff-man: \ - description=Troff Manpage Source;\ - file_extensions=.man - -application/x-troff-me: \ - description=Troff ME Macros;\ - file_extensions=.me - -application/x-troff-ms: \ - description=Troff MS Macros;\ - file_extensions=.ms - -application/x-wais-source: \ - description=Wais Source;\ - file_extensions=.src,.wsrc - -application/zip: \ - description=Zip File;\ - file_extensions=.zip;\ - icon=zip;\ - action=save - -application/x-bcpio: \ - description=Old Binary CPIO Archive;\ - file_extensions=.bcpio;\ - action=save - -application/x-cpio: \ - description=Unix CPIO Archive;\ - file_extensions=.cpio;\ - action=save - -application/x-gtar: \ - description=Gnu Tar Archive;\ - file_extensions=.gtar;\ - icon=tar;\ - action=save - -application/x-shar: \ - description=Shell Archive;\ - file_extensions=.sh,.shar;\ - action=save - -application/x-sv4cpio: \ - description=SVR4 CPIO Archive;\ - file_extensions=.sv4cpio;\ - action=save - -application/x-sv4crc: \ - description=SVR4 CPIO with CRC;\ - file_extensions=.sv4crc;\ - action=save - -application/x-tar: \ - description=Tar Archive;\ - file_extensions=.tar;\ - icon=tar;\ - action=save - -application/x-ustar: \ - description=US Tar Archive;\ - file_extensions=.ustar;\ - action=save - -audio/basic: \ - description=Basic Audio;\ - file_extensions=.snd,.au;\ - icon=audio - -audio/x-aiff: \ - description=Audio Interchange Format File;\ - file_extensions=.aifc,.aif,.aiff;\ - icon=aiff - -audio/x-wav: \ - description=Wav Audio;\ - file_extensions=.wav;\ - icon=wav;\ - action=application;\ - application=mplayer.exe %s - -image/gif: \ - description=GIF Image;\ - file_extensions=.gif;\ - icon=gif;\ - action=browser - -image/ief: \ - description=Image Exchange Format;\ - file_extensions=.ief - -image/jpeg: \ - description=JPEG Image;\ - file_extensions=.jfif,.jfif-tbnl,.jpe,.jpg,.jpeg;\ - icon=jpeg;\ - action=browser - -image/tiff: \ - description=TIFF Image;\ - file_extensions=.tif,.tiff;\ - icon=tiff - -image/vnd.fpx: \ - description=FlashPix Image;\ - file_extensions=.fpx,.fpix - -image/x-cmu-rast: \ - description=CMU Raster Image;\ - file_extensions=.ras - -image/x-portable-anymap: \ - description=PBM Anymap Image;\ - file_extensions=.pnm - -image/x-portable-bitmap: \ - description=PBM Bitmap Image;\ - file_extensions=.pbm - -image/x-portable-graymap: \ - description=PBM Graymap Image;\ - file_extensions=.pgm - -image/x-portable-pixmap: \ - description=PBM Pixmap Image;\ - file_extensions=.ppm - -image/x-rgb: \ - description=RGB Image;\ - file_extensions=.rgb - -image/x-xbitmap: \ - description=X Bitmap Image;\ - file_extensions=.xbm,.xpm - -image/x-xwindowdump: \ - description=X Window Dump Image;\ - file_extensions=.xwd - -image/png: \ - description=PNG Image;\ - file_extensions=.png;\ - icon=png;\ - action=browser - -text/html: \ - description=HTML Document;\ - file_extensions=.htm,.html;\ - icon=html - -text/plain: \ - description=Plain Text;\ - file_extensions=.text,.c,.cc,.c++,.h,.pl,.txt,.java,.el;\ - icon=text;\ - action=browser - -text/tab-separated-values: \ - description=Tab Separated Values Text;\ - file_extensions=.tsv - -text/x-setext: \ - description=Structure Enhanced Text;\ - file_extensions=.etx - -video/mpeg: \ - description=MPEG Video Clip;\ - file_extensions=.mpg,.mpe,.mpeg;\ - icon=mpeg - -video/quicktime: \ - description=QuickTime Video Clip;\ - file_extensions=.mov,.qt - -application/x-troff-msvideo: \ - description=AVI Video;\ - file_extensions=.avi;\ - icon=avi;\ - action=application;\ - application=mplayer.exe %s - -video/x-sgi-movie: \ - description=SGI Movie;\ - file_extensions=.movie,.mv - -message/rfc822: \ - description=Internet Email Message;\ - file_extensions=.mime - -application/xml: \ - description=XML document;\ - file_extensions=.xml - - diff --git a/windows-dependencies/Java/lib/deploy.jar b/windows-dependencies/Java/lib/deploy.jar deleted file mode 100644 index b378ab5fc..000000000 Binary files a/windows-dependencies/Java/lib/deploy.jar and /dev/null differ diff --git a/windows-dependencies/Java/lib/deploy/ffjcext.zip b/windows-dependencies/Java/lib/deploy/ffjcext.zip deleted file mode 100644 index d2c600f36..000000000 Binary files a/windows-dependencies/Java/lib/deploy/ffjcext.zip and /dev/null differ diff --git a/windows-dependencies/Java/lib/deploy/jqs/ff/chrome.manifest b/windows-dependencies/Java/lib/deploy/jqs/ff/chrome.manifest deleted file mode 100644 index 616531da8..000000000 --- a/windows-dependencies/Java/lib/deploy/jqs/ff/chrome.manifest +++ /dev/null @@ -1,2 +0,0 @@ -content jqs chrome/content/ -overlay chrome://browser/content/browser.xul chrome://jqs/content/overlay.xul diff --git a/windows-dependencies/Java/lib/deploy/jqs/ff/chrome/content/overlay.js b/windows-dependencies/Java/lib/deploy/jqs/ff/chrome/content/overlay.js deleted file mode 100644 index 87f46b562..000000000 --- a/windows-dependencies/Java/lib/deploy/jqs/ff/chrome/content/overlay.js +++ /dev/null @@ -1,23 +0,0 @@ -// %W% %E% -// -// Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved. -// ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. -// - -// get the JQS extension directory -const id = "jqs@sun.com"; -var ext = Components.classes["@mozilla.org/extensions/manager;1"] - .getService(Components.interfaces.nsIExtensionManager) - .getInstallLocation(id) - .getItemLocation(id); - -// create an nsILocalFile for the executable -var file = Components.classes["@mozilla.org/file/local;1"] - .createInstance(Components.interfaces.nsILocalFile); - -// construct command line -file.initWithPath(ext.path + "\\..\\..\\..\\..\\bin\\jqsnotify.exe"); - -// and launch it -file.launch(); - diff --git a/windows-dependencies/Java/lib/deploy/jqs/ff/chrome/content/overlay.xul b/windows-dependencies/Java/lib/deploy/jqs/ff/chrome/content/overlay.xul deleted file mode 100644 index cd62f885e..000000000 --- a/windows-dependencies/Java/lib/deploy/jqs/ff/chrome/content/overlay.xul +++ /dev/null @@ -1,5 +0,0 @@ - - -