diff --git a/Dockerfile b/Dockerfile index c97a33bc1..763b0e007 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,7 @@ RUN mvn dependency:go-offline -B || true # Copy source code COPY treebase-core/src treebase-core/src +COPY treebase-core/lib treebase-core/lib COPY treebase-web/src treebase-web/src COPY treebase-web/lib treebase-web/lib COPY oai-pmh_data_provider oai-pmh_data_provider @@ -45,8 +46,13 @@ COPY --from=builder /build/treebase-web/target/treebase-web.war /usr/local/tomca RUN curl -o /usr/local/tomcat/lib/postgresql.jar \ https://jdbc.postgresql.org/download/postgresql-42.7.7.jar -# Create a directory for Mesquite (placeholder) -RUN mkdir -p /usr/local/mesquite +# Create a directory for Mesquite and copy the headless Mesquite library +# The treebase-core/lib folder contains the headless Mesquite distribution with: +# - mesquite/ - Mesquite core classes +# - headless/ - Headless AWT implementation +# - com/apple/ - Apple API stubs (required by Mesquite even on non-Mac platforms) +# - Other supporting libraries +COPY --from=builder /build/treebase-core/lib /usr/local/mesquite # Set environment variables for Tomcat # Java 17 compatibility flags based on GitHub Actions workflow diff --git a/Dockerfile.dev b/Dockerfile.dev index bfff9ed90..f4523846e 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -19,6 +19,8 @@ RUN curl -o /usr/local/tomcat/lib/postgresql.jar \ https://jdbc.postgresql.org/download/postgresql-42.7.7.jar # Create Mesquite directory placeholder +# Note: In development mode, the entrypoint script will copy +# the Mesquite library from the mounted /app/treebase-core/lib RUN mkdir -p /usr/local/mesquite # Set up environment variables diff --git a/WEB_UI_ANALYSIS.md b/WEB_UI_ANALYSIS.md index 538cadb3b..2b53cdbda 100644 --- a/WEB_UI_ANALYSIS.md +++ b/WEB_UI_ANALYSIS.md @@ -416,9 +416,6 @@ styles/ | `autocomplete.js` | 3.7KB | Autocomplete widget | High - Prototype-dependent | | `menuExpandable.js` | 6.1KB | Expandable menu navigation | Medium - Pure JS possible | | `ajaxProgress.js` | 1KB | Progress indicators | Medium - Uses DWR | -| `d3.phylogram.js` | 12KB | D3-based tree rendering | Low - Uses D3 v7 | -| `newick.js` | 3.2KB | Newick format parsing | Low - Pure JavaScript | -| `sha1.js` | 4.4KB | SHA1 hashing | Low - Pure JavaScript | | `googleAnalytics.js` | 3.4KB | Analytics integration | Low - Standard GA | | `multiFileUpload.js` | 1KB | File upload handling | Medium | | `xp_progress.js` | 2.5KB | Progress bars | Medium | diff --git a/doc/technical-notes/HIBERNATE_VS_SQL_SCHEMA_ANALYSIS.md b/doc/technical-notes/HIBERNATE_VS_SQL_SCHEMA_ANALYSIS.md new file mode 100644 index 000000000..e32532474 --- /dev/null +++ b/doc/technical-notes/HIBERNATE_VS_SQL_SCHEMA_ANALYSIS.md @@ -0,0 +1,304 @@ +# Hibernate ORM vs SQL Schema Analysis + +This document provides a comprehensive analysis of discrepancies between the Hibernate ORM data model and the SQL-based PostgreSQL schema in TreeBASE. + +## Executive Summary + +**Finding**: There are discrepancies between the Hibernate ORM layer (source of truth) and the SQL schema instantiated by the database initialization scripts. However, switching to Hibernate-based schema generation introduces significant issues due to seed data dependencies. + +**Key Issues Identified**: +1. **Missing patch in CI/CD**: Patch `0011_increase-citation-column-lengths.sql` was not included in `init_db_uptodate.pg` (now fixed) +2. **Column length mismatches**: Several columns in the SQL schema have different lengths than defined in Hibernate (fixed by patches) +3. **Different initialization paths**: CI/CD and Docker use different initialization approaches +4. **Patch idempotency issues**: Some patches failed when schema already had correct types (now fixed) + +**Recommendation**: **Keep the SQL-based schema with patches** (Option A) rather than switching to Hibernate-based generation. See "Test Impact Analysis" section for reasoning. + +## Current Architecture + +### CI/CD Database Initialization +- Uses `treebase-core/db/schema/init_db_uptodate.pg` +- Applies snapshot `0000_SCHEMA_before_patches_start.sql` + `0000_DATA_before_patches_start.sql` +- Then applies patches 0001 through 0011 sequentially +- **Fixed**: Patch 0011 is now included + +### Docker Database Initialization +- Uses `docker-compose.yml` volume mounts +- Applies: + 1. `docker/00-init-roles.sql` - Role initialization + 2. `treebase-core/src/main/resources/TBASE2_POSTGRES_CREATION.sql` - Schema creation + 3. `treebase-core/src/main/resources/initTreebase.sql` - Initial data + 4. `docker/03-migration-hibernate-sequence.sql` - Hibernate sequence migration + +### Hibernate Configuration +- `hibernate.hbm2ddl.auto=` (empty/disabled) +- Uses annotation-based mapping (`@Entity`, `@Table`, `@Column`) +- Entities defined in `org.cipres.treebase.domain.*` + +## Detailed Schema Discrepancies + +### 1. Citation Table + +| Column | Hibernate Definition | SQL Schema (snapshot) | SQL Schema (TBASE2_POSTGRES_CREATION) | Patch Applied | +|--------|---------------------|----------------------|---------------------------------------|---------------| +| `title` | VARCHAR(500) | VARCHAR(500) | VARCHAR(500) | - | +| `abstract` | VARCHAR(10000) | VARCHAR(10000) | VARCHAR(10000) | - | +| `keywords` | VARCHAR(1000) | VARCHAR(255) | VARCHAR(1000) | Patch 0011 | +| `journal` | VARCHAR(500) | VARCHAR(255) | VARCHAR(500) | Patch 0011 | + +**Notes**: +- The snapshot has outdated column lengths for `keywords` (255 vs 1000) and `journal` (255 vs 500) +- Patch 0011 fixes these in the snapshot-based initialization +- `TBASE2_POSTGRES_CREATION.sql` already has correct values + +### 2. TaxonLabel Table + +| Column | Hibernate Definition | SQL Schema (snapshot) | Patch Applied | +|--------|---------------------|----------------------|---------------| +| `linked` | BOOLEAN | BOOLEAN | Patch 0010 | + +**Notes**: +- Earlier SQL had `linked` as `smallint`, but this was fixed by Patch 0010 +- Both snapshot (after patches) and TBASE2_POSTGRES_CREATION.sql now use BOOLEAN + +### 3. Help Table + +| Column | Hibernate Definition | SQL Schema | +|--------|---------------------|------------| +| `tag` | VARCHAR(255) (implicit) | VARCHAR(255) | +| `helptext` | TEXT (LOB, 65536) | TEXT | + +**Notes**: Schema matches. + +### 4. PasswordResetToken Table + +| Column | Hibernate Definition | SQL Schema (Patch 0009) | +|--------|---------------------|------------------------| +| `token_id` | BIGINT (auto-increment) | BIGINT (sequence) | +| `token` | VARCHAR(100), unique, NOT NULL | VARCHAR(100), unique, NOT NULL | +| `user_id` | BIGINT, NOT NULL | BIGINT, NOT NULL, FK | +| `expiry_date` | TIMESTAMP, NOT NULL | TIMESTAMP, NOT NULL | +| `used` | BOOLEAN, NOT NULL | BOOLEAN, NOT NULL | + +**Notes**: +- Hibernate uses `@GeneratedValue(strategy = GenerationType.IDENTITY)` +- SQL uses a sequence - these are compatible in PostgreSQL +- Schema matches structurally + +### 5. AnalysisStep Table + +| Column | Hibernate Definition | SQL Schema (snapshot) | +|--------|---------------------|----------------------| +| `tb_analysisid` | Not in Hibernate entity | VARCHAR(34) | + +**Notes**: The SQL schema has a `tb_analysisid` column that doesn't appear to be mapped in Hibernate. This is a legacy TB1 field. + +### 6. MatrixRow Table (CRITICAL - LOB Type Mismatch) + +| Column | Hibernate Definition (Before) | Hibernate Definition (After) | SQL Schema | +|--------|-------------------------------|------------------------------|------------| +| `symbolstring` | `@Lob` + `@Column(length=524288)` | `@Column(columnDefinition="text")` | TEXT | + +**Root Cause of "Bad value for type long" Error**: +The `@Lob` annotation on `MatrixRow.symbolString` caused Hibernate to use OID-based CLOB handling in PostgreSQL. However, the data is inserted via direct JDBC in `DiscreteMatrixJDBC.batchUpdateRowSymbol()` using `setString()`, which writes plain text. When Hibernate tried to read the data back using `getClob()`, it attempted to interpret the text data as a CLOB OID, causing the error: +``` +PSQLException: Bad value for type long : 0002000000000000000000000-0-0000000---00-000000 +``` + +**Solution**: Removed `@Lob` and used `@Column(columnDefinition = "text")` to ensure TEXT column type without CLOB semantics. + +### 7. PhyloTree Table (Preventive Fix) + +| Column | Hibernate Definition (Before) | Hibernate Definition (After) | SQL Schema | +|--------|-------------------------------|------------------------------|------------| +| `newickstring` | `@Lob` + `@Column(length=4194304)` | `@Column(columnDefinition="text")` | TEXT | + +**Notes**: Same pattern as MatrixRow. Removed `@Lob` to prevent potential similar issues. + +## Root Cause Analysis + +### Why Discrepancies Exist + +1. **Dual Maintenance**: The SQL schema and Hibernate annotations are maintained separately +2. **Historical Evolution**: The SQL schema evolved over time with patches while Hibernate annotations were updated independently +3. **Different Base Files**: + - `TBASE2_POSTGRES_CREATION.sql` appears more aligned with Hibernate + - The snapshot approach uses older schema + patches +4. **Missing Patch**: Patch 0011 wasn't added to the patch inclusion list + +### The Two Initialization Paths + +``` +CI/CD Path: + snapshot → patches (0001-0011) → final schema + +Docker Path: + TBASE2_POSTGRES_CREATION.sql → initTreebase.sql → final schema +``` + +These paths should produce equivalent schemas but use different mechanisms. + +## Recommendations + +### Option A: Continue with SQL-Based Schema (RECOMMENDED) +**Pros**: +- Known working approach - all tests pass +- Explicit control over schema +- Migration scripts for production +- Seed data loading order is controlled + +**Cons**: +- Dual maintenance burden +- Potential for drift between Hibernate and SQL +- Requires diligent patch management + +**Action Items**: +1. ✅ Add patch 0011 to `init_db_uptodate.pg` (DONE) +2. ✅ Make patches idempotent (DONE) +3. Consider creating a new schema snapshot that includes all patches +4. Consider adding `hibernate.hbm2ddl.auto=validate` in production + +### Option B: Switch to Hibernate-Based Schema Generation +**Pros**: +- Single source of truth (Hibernate entities) +- No impedance mismatch +- Automatic schema updates with `hbm2ddl.auto=update` + +**Cons**: +- ❌ **NOT VIABLE** - 21 test failures/errors (see Test Impact Analysis) +- Requires significant refactoring of seed data loading +- Risk of data loss in production if not carefully managed +- Less control over exact DDL + +### Option C: Hybrid Approach +**Strategy**: Use Hibernate for tests/development, SQL for production + +**Status**: Not recommended due to: +- Seed data dependency issues +- Would require rewriting test data setup +- Maintenance burden of two approaches + +## Test Impact Analysis + +### Actual Test Results + +#### SQL-Based Schema (Current Approach) +**Result**: ✅ All tests pass +``` +Tests run: 301, Failures: 0, Errors: 0, Skipped: 43 +BUILD SUCCESS +``` + +#### Hibernate-Based Schema (`hbm2ddl.auto=create`) +**Result**: ❌ 12 failures, 9 errors +``` +Tests run: 301, Failures: 12, Errors: 9, Skipped: 53 +BUILD FAILURE +``` + +**Failed Tests (Missing Seed Data)**: +- `ItemDefinitionDAOTest.testFindByDescription` +- `ItemDefinitionDAOTest.testFindPredefinedItemDefinition` +- `MatrixDAOTest.testfindKindByDescription` +- `MatrixDataTypeDAOTest.testFindByDescription` +- `AlgorithmDAOTest.testFinalAllUniqueAlgorithmDescriptions` +- `StudyStatusDAOTest.testFindStatusInProgress/Published/Ready` +- `PhyloTreeDAOTest.testFindTypeByDescription/findKindByDescription/findQualityByDescription` +- `SubmissionServiceImplTest.testProcessNexusFile` + +**Error Tests (Foreign Key Violations)**: +- `EnvironmentTest.testGetGeneratedKey` - null value in column violation +- `EnvironmentTest.testSelectFromInsert` - null value in column violation +- `MatrixServiceImplTest.testAddDelete` - NullPointerException (missing service beans) +- `AnalysisServiceImplTest.testAddDelete` - NullPointerException +- `StudyServiceImplTest.*` - Multiple NullPointerExceptions + +### Root Cause Analysis + +The test failures with Hibernate-based schema generation are caused by: + +1. **Missing Seed Data**: Hibernate creates empty tables. Tests expect pre-populated reference data for: + - `ItemDefinition` (predefined item definitions) + - `MatrixDataType` (DNA, RNA, Protein, etc.) + - `StudyStatus` (In Progress, Ready, Published) + - `TreeType`, `TreeKind`, `TreeQuality` + - `Algorithm` (reference algorithm types) + - `User`, `Person` (test user accounts) + +2. **Foreign Key Ordering**: When loading `initTreebase.sql` after Hibernate schema creation: + - Hibernate creates FK constraints immediately + - SQL script inserts data in wrong order (e.g., `user` before `person`) + - Results in FK constraint violations + +3. **Schema Already Exists Errors**: + - `password_reset_token` table created by Hibernate conflicts with SQL script + +### Conclusion + +**Hibernate-based schema generation is NOT suitable** for the current test setup because: +1. Tests depend on seed data that must be loaded in a specific order +2. The `initTreebase.sql` script was designed for SQL-based schema creation +3. Significant refactoring of test data setup would be required + +**Recommendation**: Continue with **Option A (SQL-Based Schema)** with the following improvements: +1. ✅ Keep patches idempotent (fixed in this PR) +2. ✅ Ensure all patches are included in `init_db_uptodate.pg` +3. Consider adding `hibernate.hbm2ddl.auto=validate` in production to catch drift + +### Tests to Monitor + +Based on the codebase, these test categories should be monitored: + +1. **DAO Tests** (`org.cipres.treebase.dao.*`) + - Test CRUD operations against database + - May be affected by constraint differences + +2. **Service Tests** (`org.cipres.treebase.service.*`) + - Test business logic with database + - Should be unaffected by schema generation method + +3. **Domain Tests** (`org.cipres.treebase.domain.*`) + - Test entity relationships + - May be affected by cascade/fetch settings + +## Implementation Plan + +### Phase 1: Fix Immediate Issues (COMPLETED) +1. ✅ Add patch 0011 to `init_db_uptodate.pg` + +### Phase 2: Validate Current State +1. Run full test suite with current SQL-based initialization +2. Document any existing test failures + +### Phase 3: Test Hibernate-Based Initialization +1. Create test configuration with `hbm2ddl.auto=create` +2. Run tests to identify failures +3. Document failures and root causes + +### Phase 4: Implement Chosen Strategy +1. Based on test results, implement Option A, B, or C +2. Update CI/CD configuration +3. Update Docker configuration +4. Update documentation + +## Appendix: Column Length Constants + +From `org.cipres.treebase.domain.TBPersistable`: + +```java +public static final int COLUMN_LENGTH_30 = 30; +public static final int COLUMN_LENGTH_50 = 50; +public static final int COLUMN_LENGTH_100 = 100; +public static final int COLUMN_LENGTH_STRING = 255; +public static final int COLUMN_LENGTH_500 = 500; +public static final int COLUMN_LENGTH_STRING_1K = 1000; +public static final int COLUMN_LENGTH_STRING_NOTES = 2000; +public static final int COLUMN_LENGTH_STRING_MAX = 5000; + +public static final int CITATION_TITLE_COLUMN_LENGTH = 500; +public static final int CITATION_ABSTRACT_COLUMN_LENGTH = 10000; +public static final int CITATION_KEYWORDS_COLUMN_LENGTH = 1000; +public static final int CITATION_JOURNAL_COLUMN_LENGTH = 500; +``` + +These constants define the expected column lengths in Hibernate and should be used as the reference when comparing with SQL schemas. diff --git a/docker-compose.yml b/docker-compose.yml index 3db3e1160..9b86a8a8a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,7 @@ services: - ./docker/00-init-roles.sql:/docker-entrypoint-initdb.d/00-init-roles.sql - ./treebase-core/src/main/resources/TBASE2_POSTGRES_CREATION.sql:/docker-entrypoint-initdb.d/01-schema.sql - ./treebase-core/src/main/resources/initTreebase.sql:/docker-entrypoint-initdb.d/02-init.sql + - ./docker/03-migration-hibernate-sequence.sql:/docker-entrypoint-initdb.d/03-migration-hibernate-sequence.sql healthcheck: test: ["CMD-SHELL", "pg_isready -U treebase"] interval: 10s diff --git a/docker/03-migration-hibernate-sequence.sql b/docker/03-migration-hibernate-sequence.sql new file mode 100644 index 000000000..2714b8cb2 --- /dev/null +++ b/docker/03-migration-hibernate-sequence.sql @@ -0,0 +1,14 @@ +-- Migration script to add hibernate_sequence if it doesn't exist +-- This sequence is required by Hibernate's @CollectionId annotation +-- for generating collection_id values in sub_matrix and sub_treeblock tables + +DO $$ +BEGIN + IF NOT EXISTS (SELECT FROM pg_catalog.pg_sequences WHERE sequencename = 'hibernate_sequence') THEN + CREATE SEQUENCE hibernate_sequence; + RAISE NOTICE 'Created hibernate_sequence'; + ELSE + RAISE NOTICE 'hibernate_sequence already exists'; + END IF; +END +$$; diff --git a/docker/entrypoint-dev.sh b/docker/entrypoint-dev.sh index 8f5261b69..cec27d60e 100755 --- a/docker/entrypoint-dev.sh +++ b/docker/entrypoint-dev.sh @@ -13,6 +13,15 @@ until pg_isready -h postgres -U treebase; do done echo "PostgreSQL is ready!" +# Run database migrations +echo "Running database migrations..." +if [ -f "/app/docker/03-migration-hibernate-sequence.sql" ]; then + PGPASSWORD=treebase psql -h postgres -U treebase -d treebase -f /app/docker/03-migration-hibernate-sequence.sql || echo "Warning: Migration may have failed" +else + echo "Migration script not found at /app/docker/03-migration-hibernate-sequence.sql" +fi +echo "Database migrations complete!" + # Check if we need to build the project if [ ! -f "/app/treebase-web/target/treebase-web.war" ]; then echo "Building TreeBASE web application..." @@ -81,6 +90,21 @@ if [ -d "WEB-INF/dtd" ]; then ls -la /tmp/dtd/ fi +# Copy Mesquite library files from the mounted source directory +# The treebase-core/lib folder contains the headless Mesquite distribution with: +# - mesquite/ - Mesquite core classes +# - headless/ - Headless AWT implementation +# - com/apple/ - Apple API stubs (required by Mesquite even on non-Mac platforms) +# - Other supporting libraries +if [ -d "/app/treebase-core/lib" ]; then + echo "Copying Mesquite library to /usr/local/mesquite..." + cp -r /app/treebase-core/lib/* /usr/local/mesquite/ + echo "Mesquite library installed." +else + echo "WARNING: Mesquite library not found at /app/treebase-core/lib" + echo "Nexus file parsing may not work correctly." +fi + echo "Setup complete! Starting Tomcat..." echo "========================================" echo "JSP files are mounted from: ./treebase-web/src/main/webapp" diff --git a/treebase-core/db/schema/init_db_uptodate.pg b/treebase-core/db/schema/init_db_uptodate.pg index 02b422afb..0ced32c16 100644 --- a/treebase-core/db/schema/init_db_uptodate.pg +++ b/treebase-core/db/schema/init_db_uptodate.pg @@ -11,6 +11,7 @@ \i patches/0005_add-taxonabel-tb1legacyid.sql \i patches/0006_add-algorithm-types.sql \i patches/0007_create-indices.sql - - - +\i patches/0008_add-help-messages.sql +\i patches/0009_create-password-reset-token-table.sql +\i patches/0010_fix-taxonlabel-linked-column-type.sql +\i patches/0011_increase-citation-column-lengths.sql diff --git a/treebase-core/db/schema/patches/0010_fix-taxonlabel-linked-column-type.sql b/treebase-core/db/schema/patches/0010_fix-taxonlabel-linked-column-type.sql new file mode 100644 index 000000000..7cf5d2e4c --- /dev/null +++ b/treebase-core/db/schema/patches/0010_fix-taxonlabel-linked-column-type.sql @@ -0,0 +1,28 @@ +-- Fix TAXONLABEL.linked column type from smallint to boolean +-- The Java entity expects a boolean type, but the database has smallint +-- This patch converts the column to the correct type +-- Made idempotent: only runs if column is not already boolean + +INSERT INTO versionhistory(patchnumber, patchlabel, patchdescription) +VALUES (10, 'fix-taxonlabel-linked-column-type', + 'Fix TAXONLABEL.linked column type from smallint to boolean'); + +-- Convert smallint to boolean using USING clause +-- Values 0 become FALSE, non-zero values become TRUE +-- Only apply if column is not already boolean +DO $$ +BEGIN + -- Check if the column type is not already boolean + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'taxonlabel' + AND column_name = 'linked' + AND data_type != 'boolean' + ) THEN + ALTER TABLE taxonlabel + ALTER COLUMN linked TYPE boolean + USING (linked <> 0); + END IF; +END $$; diff --git a/treebase-core/db/schema/patches/0011_increase-citation-column-lengths.sql b/treebase-core/db/schema/patches/0011_increase-citation-column-lengths.sql new file mode 100644 index 000000000..678a0b5f3 --- /dev/null +++ b/treebase-core/db/schema/patches/0011_increase-citation-column-lengths.sql @@ -0,0 +1,42 @@ +-- Increase column lengths for CITATION table to fix DataIntegrityViolationException +-- when inserting citations with long keywords or journal names +-- See GitHub issue: value too long for type character varying(255) +-- Made idempotent: only runs if column lengths are smaller than required + +INSERT INTO versionhistory(patchnumber, patchlabel, patchdescription) +VALUES (11, 'increase-citation-column-lengths', + 'Increase keywords column from 255 to 1000 and journal column from 255 to 500 to prevent data truncation errors'); + +-- Increase keywords column length from 255 to 1000 if needed +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'citation' + AND column_name = 'keywords' + AND character_maximum_length IS NOT NULL + AND character_maximum_length < 1000 + ) THEN + ALTER TABLE citation + ALTER COLUMN keywords TYPE character varying(1000); + END IF; +END $$; + +-- Increase journal column length from 255 to 500 if needed +DO $$ +BEGIN + IF EXISTS ( + SELECT 1 + FROM information_schema.columns + WHERE table_schema = 'public' + AND table_name = 'citation' + AND column_name = 'journal' + AND character_maximum_length IS NOT NULL + AND character_maximum_length < 500 + ) THEN + ALTER TABLE citation + ALTER COLUMN journal TYPE character varying(500); + END IF; +END $$; diff --git a/treebase-core/src/main/java/org/cipres/treebase/domain/TBPersistable.java b/treebase-core/src/main/java/org/cipres/treebase/domain/TBPersistable.java index bbc070f20..6ef27fa43 100644 --- a/treebase-core/src/main/java/org/cipres/treebase/domain/TBPersistable.java +++ b/treebase-core/src/main/java/org/cipres/treebase/domain/TBPersistable.java @@ -31,6 +31,8 @@ public interface TBPersistable extends NexmlWritable { // Needed to make these bigger. MJD 20090420 public static final int CITATION_TITLE_COLUMN_LENGTH = 500; public static final int CITATION_ABSTRACT_COLUMN_LENGTH = 10000; + public static final int CITATION_KEYWORDS_COLUMN_LENGTH = 1000; + public static final int CITATION_JOURNAL_COLUMN_LENGTH = 500; public Long getId(); diff --git a/treebase-core/src/main/java/org/cipres/treebase/domain/matrix/MatrixRow.java b/treebase-core/src/main/java/org/cipres/treebase/domain/matrix/MatrixRow.java index 77461c8bb..d23f147f9 100644 --- a/treebase-core/src/main/java/org/cipres/treebase/domain/matrix/MatrixRow.java +++ b/treebase-core/src/main/java/org/cipres/treebase/domain/matrix/MatrixRow.java @@ -13,7 +13,6 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.JoinColumn; -import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; @@ -163,8 +162,12 @@ public void setElements(List pNewElements) { * * @return String */ - @Lob - @Column(name = "SymbolString", length = 524288) + // Note: Removed @Lob to fix type mismatch with JDBC bypass code in DiscreteMatrixJDBC. + // The @Lob annotation causes Hibernate to use OID-based CLOB handling in PostgreSQL, + // but the JDBC code writes plain text using setString(). This mismatch causes + // "Bad value for type long" errors when Hibernate tries to read the data as CLOB OID. + // Using columnDefinition="text" ensures TEXT column type without CLOB semantics. + @Column(name = "SymbolString", columnDefinition = "text") public String getSymbolString() { return mSymbolString; } diff --git a/treebase-core/src/main/java/org/cipres/treebase/domain/nexus/mesquite/MesquiteConverter.java b/treebase-core/src/main/java/org/cipres/treebase/domain/nexus/mesquite/MesquiteConverter.java index befaa9ab7..9e2d17451 100644 --- a/treebase-core/src/main/java/org/cipres/treebase/domain/nexus/mesquite/MesquiteConverter.java +++ b/treebase-core/src/main/java/org/cipres/treebase/domain/nexus/mesquite/MesquiteConverter.java @@ -21,6 +21,7 @@ import mesquite.lib.MesquiteProject; import mesquite.lib.MesquiteTree; import mesquite.lib.MesquiteTrunk; +import mesquite.lib.MesquiteWindow; import mesquite.lib.Parser; import mesquite.lib.StringUtil; import mesquite.lib.Taxa; @@ -184,6 +185,48 @@ private void setCurrentTreeTaxa(Taxa pNewCurrentTreeTaxa) { mCurrentTreeTaxa = pNewCurrentTreeTaxa; } + /** + * Initialize Mesquite in headless mode. This method sets the necessary flags + * and handles exceptions that may occur during initialization. + * + * @param args Command line arguments to pass to Mesquite.main() + * @throws RuntimeException if Mesquite fails to initialize and mesquiteTrunk is null + */ + private static void initializeMesquiteHeadless(String[] args) { + // Set headless mode before initializing Mesquite to prevent GUI window creation + // Also set suppressAllWindows to skip all window operations + MesquiteWindow.headless = true; + MesquiteWindow.suppressAllWindows = true; + + try { + Mesquite.main(args); + setInitMesquite(true); + LOGGER.info("Mesquite initialization completed successfully"); + } catch (NullPointerException e) { + // NPE may occur during headless initialization when window creation fails + // Check if mesquiteTrunk was still initialized despite the exception + handleMesquiteInitException(e); + } catch (RuntimeException e) { + // RuntimeException (including HeadlessException) may occur during initialization + handleMesquiteInitException(e); + } + } + + /** + * Handle exceptions during Mesquite initialization. + * If mesquiteTrunk is available, log warning and continue. + * If mesquiteTrunk is null, throw RuntimeException. + */ + private static void handleMesquiteInitException(Exception e) { + if (MesquiteTrunk.mesquiteTrunk != null) { + LOGGER.warn("Mesquite initialization threw exception but trunk is available: " + e.getMessage()); + setInitMesquite(true); + } else { + LOGGER.error("Mesquite initialization failed: " + e.getMessage(), e); + throw new RuntimeException("Failed to initialize Mesquite for NEXUS parsing", e); + } + } + /** * * @see org.cipres.treebase.domain.nexus.NexusParserConverter#processLoadFile(java.util.Collection, @@ -199,8 +242,7 @@ public void processLoadFile( synchronized (MesquiteConverter.class) { if (!isInitMesquite()) { - Mesquite.main(new String[] {"-w"}); - setInitMesquite(true); + initializeMesquiteHeadless(new String[] {"-w"}); } } @@ -230,8 +272,7 @@ public void parseOneFile(File pFile, Study pStudy, NexusDataSet pDataSet) { // make sure no two calls can fight each other: synchronized (MesquiteConverter.class) { if (!isInitMesquite()) { - Mesquite.main(new String[] {"-w", "-b"}); - setInitMesquite(true); + initializeMesquiteHeadless(new String[] {"-w", "-b"}); } } diff --git a/treebase-core/src/main/java/org/cipres/treebase/domain/study/ArticleCitation.java b/treebase-core/src/main/java/org/cipres/treebase/domain/study/ArticleCitation.java index 087675a78..89448cd12 100644 --- a/treebase-core/src/main/java/org/cipres/treebase/domain/study/ArticleCitation.java +++ b/treebase-core/src/main/java/org/cipres/treebase/domain/study/ArticleCitation.java @@ -74,7 +74,7 @@ public void setVolume(String pNewVolume) { * * @return String */ - @Column(name = "Journal", length = TBPersistable.COLUMN_LENGTH_STRING) + @Column(name = "Journal", length = TBPersistable.CITATION_JOURNAL_COLUMN_LENGTH) public String getJournal() { return mJournal; } diff --git a/treebase-core/src/main/java/org/cipres/treebase/domain/study/Citation.java b/treebase-core/src/main/java/org/cipres/treebase/domain/study/Citation.java index c1cfb93ab..7ead76f5e 100644 --- a/treebase-core/src/main/java/org/cipres/treebase/domain/study/Citation.java +++ b/treebase-core/src/main/java/org/cipres/treebase/domain/study/Citation.java @@ -199,7 +199,7 @@ public void setPages(String pNewPages) { * * @return String */ - @Column(name = "Keywords", length = TBPersistable.COLUMN_LENGTH_STRING) + @Column(name = "Keywords", length = TBPersistable.CITATION_KEYWORDS_COLUMN_LENGTH) public String getKeywords() { return mKeywords; } diff --git a/treebase-core/src/main/java/org/cipres/treebase/domain/tree/PhyloTree.java b/treebase-core/src/main/java/org/cipres/treebase/domain/tree/PhyloTree.java index a831407d1..a983786d2 100644 --- a/treebase-core/src/main/java/org/cipres/treebase/domain/tree/PhyloTree.java +++ b/treebase-core/src/main/java/org/cipres/treebase/domain/tree/PhyloTree.java @@ -15,7 +15,6 @@ import javax.persistence.FetchType; import javax.persistence.FieldResult; import javax.persistence.JoinColumn; -import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.OneToOne; @@ -115,8 +114,11 @@ public PhyloTree() { * * @return String */ - @Lob - @Column(name = "NewickString", length = 4194304) + // Note: Removed @Lob to ensure consistent TEXT handling with PostgreSQL. + // The @Lob annotation causes Hibernate to use OID-based CLOB handling, + // which can cause "Bad value for type long" errors. Using columnDefinition="text" + // ensures TEXT column type without CLOB semantics. + @Column(name = "NewickString", columnDefinition = "text") public String getNewickString() { // CLOB size: 4MB. return mNewickString; diff --git a/treebase-core/src/main/resources/TBASE2_POSTGRES_CREATION.sql b/treebase-core/src/main/resources/TBASE2_POSTGRES_CREATION.sql index f48925f51..1788ecb86 100644 --- a/treebase-core/src/main/resources/TBASE2_POSTGRES_CREATION.sql +++ b/treebase-core/src/main/resources/TBASE2_POSTGRES_CREATION.sql @@ -22,7 +22,7 @@ CREATE TABLE analysis "version" integer, "name" character varying(255), notes character varying(2000), - validated smallint, + validated boolean, study_id bigint, analysis_order integer ) @@ -56,7 +56,7 @@ CREATE TABLE analyzeddata "type" character(1) NOT NULL, analyzeddata_id bigint NOT NULL, "version" integer, - "input" smallint, + "input" boolean, notes character varying(2000), treelength integer, analysisstep_id bigint NOT NULL, @@ -217,13 +217,13 @@ CREATE TABLE citation url character varying(255), abstract character varying(10000), doi character varying(255), - keywords character varying(255), + keywords character varying(1000), pages character varying(255), publishyear integer, published boolean, title character varying(500), issue character varying(255), - journal character varying(255), + journal character varying(500), volume character varying(255), isbn character varying(255), booktitle character varying(255), @@ -399,7 +399,7 @@ CREATE TABLE geneticcode codeorder character varying(255), extensions character varying(255), nucorder character varying(255), - predefined smallint, + predefined boolean, title character varying(255) ) WITH (OIDS=FALSE); @@ -464,6 +464,10 @@ CREATE SEQUENCE help_id_sequence; ALTER TABLE help ALTER COLUMN help_id SET DEFAULT nextval('help_id_sequence'); -- alter sequence help_id_sequence restart with 183; +-- hibernate_sequence is used by Hibernate's @CollectionId annotation +-- for generating collection_id values in sub_matrix and sub_treeblock tables +CREATE SEQUENCE hibernate_sequence; + CREATE TABLE itemdefinition ( itemdefinition_id bigint NOT NULL, @@ -520,10 +524,10 @@ CREATE TABLE matrix title character varying(255), "nchar" integer, ntax integer, - aligned smallint, - diagonal smallint, + aligned boolean, + diagonal boolean, triangle character varying(255), - casesensitive smallint, + casesensitive boolean, matrixdatatype_id bigint, matrixkind_id bigint, study_id bigint NOT NULL, @@ -581,10 +585,10 @@ CREATE TABLE matrixelement "type" character(1) NOT NULL, matrixelement_id bigint NOT NULL, "version" integer, - andlogic smallint, + andlogic boolean, compoundvalue character varying(1000), "value" double precision, - gap smallint, + gap boolean, matrixcolumn_id bigint, matrixrow_id bigint, itemdefinition_id bigint, @@ -672,13 +676,13 @@ CREATE TABLE phylotree phylotree_id bigint NOT NULL, "version" integer, tb1_treeid character varying(30), - bigtree smallint, + bigtree boolean, label character varying(255), ntax integer, newickstring text, nexusfilename character varying(255), published boolean, - rootedtree smallint, + rootedtree boolean, title character varying(255), rootnode_id bigint, study_id bigint NOT NULL, @@ -803,7 +807,7 @@ CREATE TABLE statechangeset ( statechangeset_id bigint NOT NULL, "version" integer, - reversible smallint, + reversible boolean, title character varying(255) ) WITH (OIDS=FALSE); @@ -983,7 +987,7 @@ CREATE TABLE taxonlabel ( taxonlabel_id bigint NOT NULL, "version" integer, - linked smallint, + linked boolean, taxonlabel character varying(255), study_id bigint, taxonvariant_id bigint @@ -1042,7 +1046,7 @@ CREATE TABLE taxonlabelset ( taxonlabelset_id bigint NOT NULL, "version" integer, - taxa smallint, + taxa boolean, title character varying(255), study_id bigint ) diff --git a/treebase-web/src/main/java/org/cipres/treebase/web/controllers/AppletInteractionController.java b/treebase-web/src/main/java/org/cipres/treebase/web/controllers/AppletInteractionController.java deleted file mode 100644 index 684b8360e..000000000 --- a/treebase-web/src/main/java/org/cipres/treebase/web/controllers/AppletInteractionController.java +++ /dev/null @@ -1,113 +0,0 @@ - - - -package org.cipres.treebase.web.controllers; - -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.io.ObjectOutputStream; -import java.io.OutputStream; -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; - -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.mvc.AbstractController; -import org.w3c.dom.CharacterData; -import org.w3c.dom.Document; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.xml.sax.InputSource; - -import org.cipres.treebase.domain.tree.PhyloTreeService; - -public class AppletInteractionController extends AbstractController { - - public static final String xmlHeader = "\n"; - - private PhyloTreeService mPhyloTreeService; - - @Override - protected ModelAndView handleRequestInternal( - HttpServletRequest pRequest, - HttpServletResponse pResponse) throws Exception { - - System.out.println("I am in AppletInteraction Controller"); - InputStream in = pRequest.getInputStream(); - ObjectInputStream inputFromApplet = new ObjectInputStream(in); - String echo = (String) inputFromApplet.readObject(); - System.out.println(echo); - inputFromApplet.close(); - in.close(); - - // echo it to the applet - OutputStream outstr = pResponse.getOutputStream(); - ObjectOutputStream oos = new ObjectOutputStream(outstr); - oos.writeObject("Sending.....OK"); - oos.flush(); - oos.close(); - outstr.flush(); - outstr.close(); - - // set the namespace context for resolving prefixes of the Qnames - // to NS URI, if the xpath expresion uses Qnames. XPath expression - // would use Qnames if the XML document uses namespaces. - // xpathprocessor.setNamespaceContext(NamespaceContext nsContext); - // create XPath expressions - - InputSource inputSource = new InputSource(new StringReader(xmlHeader + echo)); - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document doc = builder.parse(inputSource); - - NodeList trees = doc.getElementsByTagName("treeid"); - - Node treeId = trees.item(0); - - String treeid = getCharacterDataFromElement(treeId); - - NodeList newick = doc.getElementsByTagName("newickstring"); - - Node newickString = (Node) newick.item(0); - - String newickstring = getCharacterDataFromElement(newickString); - // String newickString = newick.getNodeValue(); - - System.out.println("Tree Id-1: " + treeid); - System.out.println("Newick-1: " + newickstring); - - getPhyloTreeService().updateByRearrangeNodes(new Long(treeid), newickstring); - - return null; - } - - public static String getCharacterDataFromElement(Node e) { - - List values = new ArrayList(); - Node firstChild = e.getFirstChild(); - if (firstChild instanceof CharacterData) { - CharacterData cd = (CharacterData) firstChild; - return cd.getData(); - } - return "?"; - } - - /** - * @return the phyloTreeService - */ - public PhyloTreeService getPhyloTreeService() { - return mPhyloTreeService; - } - - /** - * @param pPhyloTreeService the phyloTreeService to set - */ - public void setPhyloTreeService(PhyloTreeService pPhyloTreeService) { - mPhyloTreeService = pPhyloTreeService; - } - -} diff --git a/treebase-web/src/main/java/org/cipres/treebase/web/controllers/DirectToPhyloWidgetController.java b/treebase-web/src/main/java/org/cipres/treebase/web/controllers/DirectToPhyloWidgetController.java deleted file mode 100644 index 124071133..000000000 --- a/treebase-web/src/main/java/org/cipres/treebase/web/controllers/DirectToPhyloWidgetController.java +++ /dev/null @@ -1,69 +0,0 @@ - - - -package org.cipres.treebase.web.controllers; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.mvc.Controller; - -import org.cipres.treebase.domain.tree.PhyloTree; -import org.cipres.treebase.domain.tree.PhyloTreeService; - -/** - * - * @author madhu - * - * Created on, October 11, 2007 - * - */ - -public class DirectToPhyloWidgetController implements Controller { - - private PhyloTreeService mPhyloTreeService; - protected String mDefaultView; - - public PhyloTreeService getPhyloTreeService() { - return mPhyloTreeService; - } - - - public String getDefaultView() { - return mDefaultView; - } - - public void setDefaultView(String defaultView) { - this.mDefaultView = defaultView; - } - - /** - * Set the PhyloTreeService - */ - public void setPhyloTreeService(PhyloTreeService pNewPhyloTreeService) { - mPhyloTreeService = pNewPhyloTreeService; - } - - public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) - throws Exception { - - if (request.getParameter("treeid") != null) { - - PhyloTree pt = getPhyloTreeService().findByID( - Long.parseLong(request.getParameter("treeid"))); - - Map treemap = new HashMap(); - treemap.put("TreeId:" + pt.getId(), pt.getNewickString()); - request.getSession().setAttribute("NEWICKSTRINGSMAP", treemap); - - } - - return new ModelAndView(getDefaultView()); - - } - -} diff --git a/treebase-web/src/main/webapp/WEB-INF/pages/analysisSection-Piece.jsp b/treebase-web/src/main/webapp/WEB-INF/pages/analysisSection-Piece.jsp index e213311d0..e16b1e821 100644 --- a/treebase-web/src/main/webapp/WEB-INF/pages/analysisSection-Piece.jsp +++ b/treebase-web/src/main/webapp/WEB-INF/pages/analysisSection-Piece.jsp @@ -1,6 +1,6 @@ <%@ include file="/common/taglibs.jsp"%> - +
  • Software Used: @@ -24,7 +24,7 @@ - + diff --git a/treebase-web/src/main/webapp/WEB-INF/pages/search/study/treeBlocks.jsp b/treebase-web/src/main/webapp/WEB-INF/pages/search/study/treeBlocks.jsp index 9e42afdfc..3e326c698 100644 --- a/treebase-web/src/main/webapp/WEB-INF/pages/search/study/treeBlocks.jsp +++ b/treebase-web/src/main/webapp/WEB-INF/pages/search/study/treeBlocks.jsp @@ -67,7 +67,7 @@ " >View trees --%> - View trees + View trees <%if(request.isUserInRole("Admin") || request.isUserInRole("Associate Editor")){%> diff --git a/treebase-web/src/main/webapp/WEB-INF/pages/search/study/trees.jsp b/treebase-web/src/main/webapp/WEB-INF/pages/search/study/trees.jsp index 32992fecf..58d228729 100644 --- a/treebase-web/src/main/webapp/WEB-INF/pages/search/study/trees.jsp +++ b/treebase-web/src/main/webapp/WEB-INF/pages/search/study/trees.jsp @@ -12,13 +12,7 @@ <% pageContext.setAttribute("accesscode",request.getSession().getAttribute("x-access-code")); %> - + - Tr${tree.id} + + + + + + + Tr${tree.id} - + + + + + + + " diff --git a/treebase-web/src/main/webapp/WEB-INF/pages/search/treeList.jsp b/treebase-web/src/main/webapp/WEB-INF/pages/search/treeList.jsp index 957481b14..17666c34d 100644 --- a/treebase-web/src/main/webapp/WEB-INF/pages/search/treeList.jsp +++ b/treebase-web/src/main/webapp/WEB-INF/pages/search/treeList.jsp @@ -4,15 +4,6 @@ <%--content tag="heading"> - - - Tr${tree.id} + + + + Tr${tree.id} @@ -85,7 +79,10 @@ sortable="false" class="iconColumn" headerClass="iconColumn"> - + + + + " @@ -117,7 +114,7 @@ - - + + diff --git a/treebase-web/src/main/webapp/WEB-INF/pages/treeBlockList.jsp b/treebase-web/src/main/webapp/WEB-INF/pages/treeBlockList.jsp index b6527f3f6..1f505c166 100644 --- a/treebase-web/src/main/webapp/WEB-INF/pages/treeBlockList.jsp +++ b/treebase-web/src/main/webapp/WEB-INF/pages/treeBlockList.jsp @@ -87,18 +87,18 @@ Tree blocks alt=""/> - - - - " - title="" - alt=""/> - + + + + " + title="" + alt=""/> +

    Table below shows a list of trees belonging to a Block with ID: and Title:

    -
    -
    - -Trees -" /> + +
    + +Trees +" /> @@ -40,19 +40,19 @@ Trees "/> " /> - - - - - - - + + + + + + + @@ -80,86 +80,86 @@ Trees - - - - - " - title="" - alt=""/> - - - " - title="" - alt=""/> - - - - + + + + + " + title="" + alt=""/> + + + " + title="" + alt=""/> + + + + - - " - title="" - alt=""/> + + " + title="" + alt=""/> - - - " - title="" - alt=""/> - - - " - title="" + url="/user/downloadATree.html" + paramId="treeid" + paramProperty="id" + class="iconColumn" + headerClass="iconColumn"> + " + title="" + alt=""/> + + + + " + title="" alt=""/> - - - - - " - title="" - alt=""/> - + + + + + " + title="" + alt=""/> + @@ -183,3 +183,4 @@ Trees
    + diff --git a/treebase-web/src/main/webapp/WEB-INF/pages/treeViewer.jsp b/treebase-web/src/main/webapp/WEB-INF/pages/treeViewer.jsp index 80f500a3c..9d2ac96b2 100644 --- a/treebase-web/src/main/webapp/WEB-INF/pages/treeViewer.jsp +++ b/treebase-web/src/main/webapp/WEB-INF/pages/treeViewer.jsp @@ -7,15 +7,49 @@ Tree Viewer - - - - - + + + + + + + + + + + + +
    + + +
    Select a tree from the list to view it.
    @@ -155,21 +328,21 @@ legend {
      -
    1. - ${tree.label} + - ${tree.title} + - Tree ${tree.id} + Tree
    2. @@ -188,13 +361,13 @@ legend {
    - - diff --git a/treebase-web/src/main/webapp/WEB-INF/treebase-servlet.xml b/treebase-web/src/main/webapp/WEB-INF/treebase-servlet.xml index fe1119cff..48d7cf12b 100644 --- a/treebase-web/src/main/webapp/WEB-INF/treebase-servlet.xml +++ b/treebase-web/src/main/webapp/WEB-INF/treebase-servlet.xml @@ -28,11 +28,9 @@ - + - - - + @@ -627,10 +625,7 @@ redirect:/user/treeList.html - - - treeViewer - + @@ -1004,7 +999,7 @@ deleteAMatrixController deleteARowSegmentController readOnlyListTreeController - directToPhyloWidgetController + directMapToPhyloWidgetController nexusFilesController uploadRowSegmentDataController @@ -1013,7 +1008,7 @@ exportRowSegmentDataController exportRowSegmentTemplateController - appletInteractionController + treeParserController diff --git a/treebase-web/src/main/webapp/common/adminMenu.jsp b/treebase-web/src/main/webapp/common/adminMenu.jsp index 5fa55851c..f5783ea31 100644 --- a/treebase-web/src/main/webapp/common/adminMenu.jsp +++ b/treebase-web/src/main/webapp/common/adminMenu.jsp @@ -4,7 +4,6 @@