diff --git a/goobi-viewer-core/.project b/goobi-viewer-core/.project index f33cfbcfb19..980db90ff08 100644 --- a/goobi-viewer-core/.project +++ b/goobi-viewer-core/.project @@ -50,7 +50,7 @@ config_viewer.xml 1 - PARENT-5-PROJECT_LOC/opt/digiverso/viewer/config/config_viewer.xml + /opt/digiverso/viewer/config/config_viewer.xml diff --git a/goobi-viewer-core/.settings/org.eclipse.wst.common.component b/goobi-viewer-core/.settings/org.eclipse.wst.common.component index c6d1631e25e..7598e2d97f0 100644 --- a/goobi-viewer-core/.settings/org.eclipse.wst.common.component +++ b/goobi-viewer-core/.settings/org.eclipse.wst.common.component @@ -1,9 +1,17 @@ + + + + + + + + diff --git a/goobi-viewer-core/package-lock.json b/goobi-viewer-core/package-lock.json index 324200db6d0..80e40cd2b3e 100644 --- a/goobi-viewer-core/package-lock.json +++ b/goobi-viewer-core/package-lock.json @@ -19,23 +19,6 @@ "regenerator-runtime": "^0.13.4" } }, - "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, "@types/acorn": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/@types/acorn/-/acorn-4.0.5.tgz", @@ -84,9 +67,9 @@ "dev": true }, "@types/node": { - "version": "14.11.8", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.11.8.tgz", - "integrity": "sha512-KPcKqKm5UKDkaYPTuXSx8wEP7vE9GnuaXIZKijwRYcePpZFDVuy2a57LarFKiORbHOuTOOwYzxVxcUzsh2P2Pw==", + "version": "14.14.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.7.tgz", + "integrity": "sha512-Zw1vhUSQZYw+7u5dAwNbIA9TuTotpzY/OF7sJM9FqPOF3SPjKnxrjoTktXDZgUjybf4cWVBP7O8wvKdSaGHweg==", "dev": true }, "@webassemblyjs/ast": { @@ -319,16 +302,6 @@ "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=", "dev": true }, - "aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, "ajv": { "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", @@ -1578,62 +1551,6 @@ "integrity": "sha1-NWnt6Lo0MV+rmcPpLLBMciDeH6g=", "dev": true }, - "cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", - "dev": true, - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.0", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "dependencies": { - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -1733,12 +1650,6 @@ "upath": "^1.1.1" } }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true - }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -1771,12 +1682,6 @@ } } }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, "cli-color": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/cli-color/-/cli-color-0.3.3.tgz", @@ -1848,6 +1753,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colorette": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", + "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", + "dev": true + }, "colors": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", @@ -1869,12 +1780,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -2421,9 +2326,9 @@ } }, "enhanced-resolve": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.2.0.tgz", - "integrity": "sha512-NZlGLl8DxmZoq0uqPPtJfsCAir68uR047+Udsh1FH4+5ydGQdMurn/A430A1BtxASVmMEuS7/XiJ5OxJ9apAzQ==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.3.1.tgz", + "integrity": "sha512-G1XD3MRGrGfNcf6Hg0LVZG7GIKcYkbfHa5QMxt1HDUTdYoXH0JR1xXyg+MaKLF73E9A27uWNVxvFivNRYeUB6w==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -2985,17 +2890,6 @@ } } }, - "find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, "find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -3138,15 +3032,6 @@ } } }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -4535,30 +4420,12 @@ "dev": true, "optional": true }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true - }, "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=", "dev": true }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -4860,6 +4727,12 @@ } } }, + "is-docker": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", + "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", + "dev": true + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", @@ -4968,10 +4841,13 @@ "dev": true }, "is-wsl": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", - "integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", - "dev": true + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "requires": { + "is-docker": "^2.0.0" + } }, "isarray": { "version": "1.0.0", @@ -5016,9 +4892,9 @@ "dev": true }, "jest-worker": { - "version": "26.5.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", - "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", + "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", "dev": true, "requires": { "@types/node": "*", @@ -5365,12 +5241,24 @@ } }, "karma-firefox-launcher": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz", - "integrity": "sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-2.1.0.tgz", + "integrity": "sha512-dkiyqN2R6fCWt78rciOXJLFDWcQ7QEQi++HgebPJlw1y0ycDjGNDHuSrhdh48QG02fzZKK20WHFWVyBZ6CPngg==", "dev": true, "requires": { - "is-wsl": "^2.1.0" + "is-wsl": "^2.2.0", + "which": "^2.0.1" + }, + "dependencies": { + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } } }, "karma-htmlfile-reporter": { @@ -5788,15 +5676,6 @@ } } }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, "lru-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz", @@ -5806,23 +5685,6 @@ "es5-ext": "~0.10.2" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, "make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -5983,52 +5845,6 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - } - }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -6161,9 +5977,9 @@ "dev": true }, "node-releases": { - "version": "1.1.61", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.61.tgz", - "integrity": "sha512-DD5vebQLg8jLCOzwupn954fbIiZht05DAZs0k2u8NStSe6h9XdsuIQL8hSRKYiU8WUQRznmSDrKGbv3ObOmC7g==", + "version": "1.1.66", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.66.tgz", + "integrity": "sha512-JHEQ1iWPGK+38VLB2H9ef2otU4l8s3yAMt9Xf934r6+ojCYDMHPMqvCc9TnzfeFSP1QEOeU6YZEd3+De0LTCgg==", "dev": true }, "nopt": { @@ -6557,15 +6373,6 @@ "p-limit": "^2.2.0" } }, - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, "p-throttler": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/p-throttler/-/p-throttler-0.1.0.tgz", @@ -6889,12 +6696,6 @@ "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", "dev": true }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, "promptly": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/promptly/-/promptly-0.2.0.tgz", @@ -7936,15 +7737,6 @@ "tweetnacl": "~0.14.0" } }, - "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", @@ -8147,33 +7939,11 @@ "dev": true }, "tapable": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.0.0.tgz", - "integrity": "sha512-bjzn0C0RWoffnNdTzNi7rNDhs1Zlwk2tRXgk8EiHKAOX1Mag3d6T0Y5zNa7l9CJ+EoUne/0UHdwS8tMbkh9zDg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.1.1.tgz", + "integrity": "sha512-Wib1S8m2wdpLbmQz0RBEVosIyvb/ykfKXf3ZIDqvWoMg/zTNm6G/tDSuUM61J1kNCDXWJrLHGSFeMhAG+gAGpQ==", "dev": true }, - "tar": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.0.5.tgz", - "integrity": "sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg==", - "dev": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, "tar-fs": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-0.5.2.tgz", @@ -8262,9 +8032,9 @@ } }, "terser": { - "version": "5.3.5", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.5.tgz", - "integrity": "sha512-Qw3CZAMmmfU824AoGKalx+riwocSI5Cs0PoGp9RdSLfmxkmJgyBxqLBP/isDNtFyhHnitikvRMZzyVgeq+U+Tg==", + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", + "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", "dev": true, "requires": { "commander": "^2.20.0", @@ -8281,20 +8051,17 @@ } }, "terser-webpack-plugin": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", - "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.0.3.tgz", + "integrity": "sha512-zFdGk8Lh9ZJGPxxPE6jwysOlATWB8GMW8HcfGULWA/nPal+3VdATflQvSBSLQJRCmYZnfFJl6vkRTiwJGNgPiQ==", "dev": true, "requires": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.5.0", + "jest-worker": "^26.6.1", "p-limit": "^3.0.2", "schema-utils": "^3.0.0", "serialize-javascript": "^5.0.1", "source-map": "^0.6.1", - "terser": "^5.3.4", - "webpack-sources": "^1.4.3" + "terser": "^5.3.8" }, "dependencies": { "p-limit": { @@ -8311,16 +8078,6 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true - }, - "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } } } }, @@ -8648,24 +8405,6 @@ "set-value": "^2.0.1" } }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -8869,9 +8608,9 @@ } }, "watchpack": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.0.0.tgz", - "integrity": "sha512-xSdCxxYZWNk3VK13bZRYhsQpfa8Vg63zXG+3pyU8ouqSLRCv4IGXIp9Kr226q6GBkGRlZrST2wwKtjfKz2m7Cg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.0.1.tgz", + "integrity": "sha512-vO8AKGX22ZRo6PiOFM9dC0re8IcKh8Kd/aH2zeqUc6w4/jBGlTy2P7fTC6ekT0NjVeGjgU2dGC5rNstKkeLEQg==", "dev": true, "requires": { "glob-to-regexp": "^0.4.1", @@ -8879,9 +8618,9 @@ } }, "webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.0.0.tgz", - "integrity": "sha512-OK+Q9xGgda3idw/DgCf75XsVFxRLPu48qPwygqI3W9ls5sDdKif5Ay4SM/1UVob0w4juJy14Zv9nNv0WeyV0aA==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.4.0.tgz", + "integrity": "sha512-udpYTyqz8toTTdaOsL2QKPLeZLt2IEm9qY7yTXuFEQhKu5bk0yQD9BtAdVQksmz4jFbbWOiWmm3NHarO0zr/ng==", "dev": true, "requires": { "@types/eslint-scope": "^3.7.0", @@ -8890,11 +8629,11 @@ "@webassemblyjs/helper-module-context": "1.9.0", "@webassemblyjs/wasm-edit": "1.9.0", "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^8.0.3", - "browserslist": "^4.14.3", + "acorn": "^8.0.4", + "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.2.0", - "eslint-scope": "^5.1.0", + "enhanced-resolve": "^5.3.1", + "eslint-scope": "^5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.4", @@ -8905,9 +8644,9 @@ "pkg-dir": "^4.2.0", "schema-utils": "^3.0.0", "tapable": "^2.0.0", - "terser-webpack-plugin": "^4.1.0", + "terser-webpack-plugin": "^5.0.3", "watchpack": "^2.0.0", - "webpack-sources": "^2.0.1" + "webpack-sources": "^2.1.1" }, "dependencies": { "@types/estree": { @@ -8923,27 +8662,28 @@ "dev": true }, "browserslist": { - "version": "4.14.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.5.tgz", - "integrity": "sha512-Z+vsCZIvCBvqLoYkBFTwEYH3v5MCQbsAjp50ERycpOjnPmolg1Gjy4+KaWWpm8QOJt9GHkhdqAl14NpCX73CWA==", + "version": "4.14.7", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.7.tgz", + "integrity": "sha512-BSVRLCeG3Xt/j/1cCGj1019Wbty0H+Yvu2AOuZSuoaUWn3RatbL33Cxk+Q4jRMRAbOm0p7SLravLjpnT6s0vzQ==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001135", - "electron-to-chromium": "^1.3.571", - "escalade": "^3.1.0", - "node-releases": "^1.1.61" + "caniuse-lite": "^1.0.30001157", + "colorette": "^1.2.1", + "electron-to-chromium": "^1.3.591", + "escalade": "^3.1.1", + "node-releases": "^1.1.66" } }, "caniuse-lite": { - "version": "1.0.30001148", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001148.tgz", - "integrity": "sha512-E66qcd0KMKZHNJQt9hiLZGE3J4zuTqE1OnU53miEVtylFbwOEmeA5OsRu90noZful+XGSQOni1aT2tiqu/9yYw==", + "version": "1.0.30001157", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001157.tgz", + "integrity": "sha512-gOerH9Wz2IRZ2ZPdMfBvyOi3cjaz4O4dgNwPGzx8EhqAs4+2IL/O+fJsbt+znSigujoZG8bVcIAUM/I/E5K3MA==", "dev": true }, "electron-to-chromium": { - "version": "1.3.578", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.578.tgz", - "integrity": "sha512-z4gU6dA1CbBJsAErW5swTGAaU2TBzc2mPAonJb00zqW1rOraDo2zfBMDRvaz9cVic+0JEZiYbHWPw/fTaZlG2Q==", + "version": "1.3.595", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.595.tgz", + "integrity": "sha512-JpaBIhdBkF9FLG7x06ONfe0f5bxPrxRcq0X+Sc8vsCt+OPWIzxOD+qM71NEHLGbDfN9Q6hbtHRv4/dnvcOxo6g==", "dev": true }, "graceful-fs": { @@ -8976,9 +8716,9 @@ } }, "webpack-sources": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.0.1.tgz", - "integrity": "sha512-A9oYz7ANQBK5EN19rUXbvNgfdfZf5U2gP0769OXsj9CvYkCR6OHOsd6OKyEy4H38GGxpsQPKIL83NC64QY6Xmw==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", + "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", "dev": true, "requires": { "source-list-map": "^2.0.1", @@ -9150,12 +8890,6 @@ "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", "dev": true }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, "yargs": { "version": "15.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", diff --git a/goobi-viewer-core/package.json b/goobi-viewer-core/package.json index 173b927d472..e20282b922e 100644 --- a/goobi-viewer-core/package.json +++ b/goobi-viewer-core/package.json @@ -25,7 +25,7 @@ "karma": "^5.2.3", "karma-chrome-launcher": "^3.1.0", "karma-cli": "*", - "karma-firefox-launcher": "^1.3.0", + "karma-firefox-launcher": "^2.1.0", "karma-htmlfile-reporter": "^0.3.8", "karma-jasmine": "^4.0.1", "karma-jasmine-jquery": "*", @@ -35,7 +35,7 @@ "less-plugin-autoprefix": "^2.0.0", "michelangelo": "^0.8.0", "pdfjs-dist": "^2.5.207", - "webpack": "5.0.0" + "webpack": "5.4.0" }, "scripts": { "docs": "jsdoc -c jsdoc.conf", diff --git a/goobi-viewer-core/pom.xml b/goobi-viewer-core/pom.xml index 7a8702556af..d26b3fc58b5 100644 --- a/goobi-viewer-core/pom.xml +++ b/goobi-viewer-core/pom.xml @@ -6,10 +6,9 @@ 4.0.0 io.goobi.viewer viewer-core - 4.12.2 + 4.13.0 jar - Goobi viewer - Core The Goobi viewer is an open source solution for the presentation of digitized cultural assets. https://github.com/intranda/goobi-viewer-core @@ -58,7 +57,6 @@ - 20.11 UTF-8 1.8 true @@ -76,7 +74,7 @@ 1.5.5 2.5.0 1.2.0 - 1.9.2 + 1.10.0-SNAPSHOT 1.5.2 @@ -95,11 +93,11 @@ 2.7.7 1.4.200 4.5.13 - 4.4.13 + 4.4.14 1.7.3 68.1 1 - 2.11.3 + 2.12.0 1.3.5 2.3.3 1.3.2 @@ -113,13 +111,14 @@ 2.0.6 2.32 2.26-b03 - 1.2 - 20200518 + 1.2 + + 20201115 1.13.1 1.2 4.13.1 2.3.0 - 3.6.0 + 3.6.28 5.11.2 1.0.2 1.3.23 @@ -130,11 +129,11 @@ 2.1.1 4.0.1 1.7.30 - 8.6.3 + 8.7.0 4.0.0 1.0-2 - 2.1.5 - 9.0.39 + 2.1.6 + 9.0.40 3.1.5.SP1 2.12.0 2.6.2 @@ -401,7 +400,6 @@ org.glassfish.jersey.test-framework jersey-test-framework-core ${jersey.version} - test org.glassfish.jersey.test-framework.providers diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/AbstractApiUrlManager.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/AbstractApiUrlManager.java index 3eede36781a..a3175328d80 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/AbstractApiUrlManager.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/AbstractApiUrlManager.java @@ -270,7 +270,7 @@ public ApiInfo getInfo() { client.property(ClientProperties.CONNECT_TIMEOUT, 5000); client.property(ClientProperties.READ_TIMEOUT, 5000); return client - .target(getApiUrl()) + .target(getApiUrl() + "/") .request(MediaType.APPLICATION_JSON) .get(ApiInfo.class); } catch (Throwable e) { diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/resourcebuilders/IIIFPresentationResourceBuilder.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/resourcebuilders/IIIFPresentationResourceBuilder.java index c8a7687b696..3e26cd23aa3 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/resourcebuilders/IIIFPresentationResourceBuilder.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/resourcebuilders/IIIFPresentationResourceBuilder.java @@ -15,7 +15,8 @@ */ package io.goobi.viewer.api.rest.resourcebuilders; -import static io.goobi.viewer.api.rest.v1.ApiUrls.*; +import static io.goobi.viewer.api.rest.v1.ApiUrls.RECORDS_IMAGE_IIIF; +import static io.goobi.viewer.api.rest.v1.ApiUrls.RECORDS_RECORD; import java.io.IOException; import java.net.URI; @@ -34,7 +35,6 @@ import org.apache.solr.common.SolrDocumentList; import de.intranda.api.annotation.oa.Motivation; -import de.intranda.api.iiif.discovery.OrderedCollectionPage; import de.intranda.api.iiif.presentation.AbstractPresentationModelElement; import de.intranda.api.iiif.presentation.AnnotationList; import de.intranda.api.iiif.presentation.Canvas; @@ -63,7 +63,9 @@ import io.goobi.viewer.model.iiif.presentation.builder.OpenAnnotationBuilder; import io.goobi.viewer.model.iiif.presentation.builder.SequenceBuilder; import io.goobi.viewer.model.iiif.presentation.builder.StructureBuilder; +import io.goobi.viewer.model.search.SearchHelper; import io.goobi.viewer.model.viewer.BrowseDcElement; +import io.goobi.viewer.model.viewer.PageType; import io.goobi.viewer.model.viewer.PhysicalElement; import io.goobi.viewer.model.viewer.StringPair; import io.goobi.viewer.model.viewer.StructElement; @@ -85,7 +87,7 @@ public class IIIFPresentationResourceBuilder { public IIIFPresentationResourceBuilder(AbstractApiUrlManager urls, HttpServletRequest request) { this.urls = urls; - this.request = request; + this.request = request; } public IPresentationModelElement getManifest(String pi, BuildMode mode) throws PresentationException, IndexUnreachableException, @@ -130,23 +132,42 @@ public Range getRange(String pi, String logId) throws PresentationException, Ind return range.orElseThrow(() -> new ContentNotFoundException("Not document with PI = " + pi + " and logId = " + logId + " found")); } - public Sequence getBaseSequence(String pi, BuildMode buildMode) throws PresentationException, IndexUnreachableException, URISyntaxException, + public Sequence getBaseSequence(String pi, BuildMode buildMode, String preferedViewName) + throws PresentationException, IndexUnreachableException, URISyntaxException, ViewerConfigurationException, DAOException, IllegalRequestException, ContentNotFoundException { StructElement doc = getManifestBuilder().getDocument(pi); - + PageType preferedView = getPreferedPageTypeForCanvas(preferedViewName); + IPresentationModelElement manifest = new ManifestBuilder(urls).setBuildMode(buildMode).generateManifest(doc); if (manifest instanceof Collection) { throw new IllegalRequestException("Identifier refers to a collection which does not have a sequence"); } else if (manifest instanceof Manifest) { - new SequenceBuilder(urls).setBuildMode(buildMode).addBaseSequence((Manifest) manifest, doc, manifest.getId().toString(), request); + new SequenceBuilder(urls).setBuildMode(buildMode) + .setPreferedView(preferedView) + .addBaseSequence((Manifest) manifest, doc, manifest.getId().toString(), request); return ((Manifest) manifest).getSequences().get(0); } throw new ContentNotFoundException("Not manifest with identifier " + pi + " found"); } + /** + * @param preferedViewName + * @return + */ + public PageType getPreferedPageTypeForCanvas(String preferedViewName) { + PageType preferedView = PageType.viewObject; + if (StringUtils.isNotBlank(preferedViewName)) { + preferedView = PageType.getByName(preferedViewName); + if (preferedView == PageType.other) { + preferedView = PageType.viewObject; + } + } + return preferedView; + } + public Layer getLayer(String pi, String typeName, BuildMode buildMode) throws PresentationException, IndexUnreachableException, URISyntaxException, ViewerConfigurationException, DAOException, ContentNotFoundException, IllegalRequestException, IOException { StructElement doc = getStructureBuilder().getDocument(pi); @@ -169,19 +190,20 @@ public Layer getLayer(String pi, String typeName, BuildMode buildMode) throws Pr return layer; } } - + /** * @param pi * @param pageNo * @return - * @throws ViewerConfigurationException - * @throws URISyntaxException - * @throws ContentNotFoundException - * @throws IndexUnreachableException - * @throws PresentationException - * @throws DAOException + * @throws ViewerConfigurationException + * @throws URISyntaxException + * @throws ContentNotFoundException + * @throws IndexUnreachableException + * @throws PresentationException + * @throws DAOException */ - public IPresentationModelElement getCanvas(String pi, Integer pageNo) throws URISyntaxException, ViewerConfigurationException, ContentNotFoundException, PresentationException, IndexUnreachableException, DAOException { + public IPresentationModelElement getCanvas(String pi, Integer pageNo) throws URISyntaxException, ViewerConfigurationException, + ContentNotFoundException, PresentationException, IndexUnreachableException, DAOException { StructElement doc = getManifestBuilder().getDocument(pi); if (doc != null) { PhysicalElement page = getSequenceBuilder().getPage(doc, pageNo); @@ -245,7 +267,7 @@ public LayerBuilder getLayerBuilder() { } return layerBuilder; } - + /** * Returns a iiif collection of all collections from the given solr-field The response includes the metadata and subcollections of the topmost * collections. Child collections may be accessed following the links in the @id properties in the member-collections Requires passing a language @@ -267,7 +289,7 @@ public Collection getCollections(String collectionField) return collection; } - + /** * Returns a iiif collection of all collections from the given solr-field The response includes the metadata and subcollections of the topmost * collections. Child collections may be accessed following the links in the @id properties in the member-collections Requires passing a language @@ -285,9 +307,9 @@ public Collection getCollectionsWithGrouping(String collectionField, String grou Collection collection = getCollectionBuilder().generateCollection(collectionField, null, groupingField, DataManager.getInstance().getConfiguration().getCollectionSplittingChar(collectionField)); - + getCollectionBuilder().addTagListService(collection, collectionField, groupingField, "grouping"); - + return collection; } @@ -315,38 +337,43 @@ public Collection getCollection(String collectionField, String topElement) return collection; } - - - public List getManifestsForQuery(String query, String sortFields, int first, int rows) throws DAOException, PresentationException, IndexUnreachableException, URISyntaxException, ViewerConfigurationException { - String finalQuery = query + " +(ISWORK:* OR ISANCHOR:*)"; - + public List getManifestsForQuery(String query, String sortFields, int first, int rows) + throws DAOException, PresentationException, IndexUnreachableException, URISyntaxException, ViewerConfigurationException { + + String finalQuery = query + " " + SearchHelper.ALL_RECORDS_QUERY; + List sortFieldList = SolrSearchIndex.getSolrSortFieldsAsList(sortFields == null ? "" : sortFields, ",", " "); - SolrDocumentList queryResults = DataManager.getInstance().getSearchIndex().search(finalQuery, first, rows, sortFieldList, null, Arrays.asList(CollectionBuilder.CONTAINED_WORKS_QUERY_FIELDS)).getResults(); - + SolrDocumentList queryResults = DataManager.getInstance() + .getSearchIndex() + .search(finalQuery, first, rows, sortFieldList, null, Arrays.asList(CollectionBuilder.CONTAINED_WORKS_QUERY_FIELDS)) + .getResults(); + List manifests = new ArrayList<>(queryResults.size()); ManifestBuilder builder = new ManifestBuilder(urls); for (SolrDocument doc : queryResults) { long luceneId = Long.parseLong(doc.getFirstValue(SolrConstants.IDDOC).toString()); StructElement ele = new StructElement(luceneId, doc); AbstractPresentationModelElement manifest = builder.generateManifest(ele); - + AbstractApiUrlManager imageUrls = DataManager.getInstance().getRestApiManager().getContentApiManager(); - - if(manifest.getThumbnails().isEmpty()) { + + if (imageUrls != null && manifest.getThumbnails().isEmpty()) { int thumbsWidth = DataManager.getInstance().getConfiguration().getThumbnailsWidth(); int thumbsHeight = DataManager.getInstance().getConfiguration().getThumbnailsHeight(); - String thumbnailUrl = imageUrls.path(RECORDS_RECORD, RECORDS_IMAGE_IIIF).params(ele.getPi(), "full", "!" + thumbsWidth + "," + thumbsHeight, 0, "default", "jpg").build(); + String thumbnailUrl = imageUrls.path(RECORDS_RECORD, RECORDS_IMAGE_IIIF) + .params(ele.getPi(), "full", "!" + thumbsWidth + "," + thumbsHeight, 0, "default", "jpg") + .build(); manifest.addThumbnail(new ImageContent(URI.create(thumbnailUrl))); } - + manifests.add(manifest); } - + return manifests; } - + /** * Returns a iiif collection of the given topCollection for the give collection field The response includes the metadata and subcollections of the * direct child collections. Collections further down the hierarchy may be accessed following the links in the @id properties in the @@ -354,7 +381,8 @@ public List getManifestsForQuery(String query, String * * @param collectionField a {@link java.lang.String} object. * @param topElement a {@link java.lang.String} object. - * @param groupingField a solr field by which the collections may be grouped. Included in the response for each {@link BrowseDcElement} to enable grouping by client + * @param groupingField a solr field by which the collections may be grouped. Included in the response for each {@link BrowseDcElement} to enable + * grouping by client * @return a {@link de.intranda.api.iiif.presentation.Collection} object. * @throws io.goobi.viewer.exceptions.IndexUnreachableException if any. * @throws java.net.URISyntaxException if any. @@ -369,7 +397,7 @@ public Collection getCollectionWithGrouping(String collectionField, String topEl DataManager.getInstance().getConfiguration().getCollectionSplittingChar(collectionField)); getCollectionBuilder().addTagListService(collection, collectionField, facetField, "grouping"); - + return collection; } @@ -392,5 +420,4 @@ public CollectionBuilder getCollectionBuilder() { return collectionBuilder; } - } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/ApplicationResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/ApplicationResource.java index de6eab0ca1e..11460a6baa7 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/ApplicationResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/ApplicationResource.java @@ -15,13 +15,16 @@ */ package io.goobi.viewer.api.rest.v1; +import java.util.List; +import java.util.stream.Collectors; + import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; -import io.goobi.viewer.Version; +import de.intranda.monitoring.timer.TimingStatistics; import io.goobi.viewer.api.rest.AbstractApiUrlManager; import io.goobi.viewer.api.rest.AbstractApiUrlManager.ApiInfo; import io.goobi.viewer.controller.DataManager; @@ -45,4 +48,13 @@ public ApiInfo getApiInfo() { info.specification = urls.getApiUrl() + "/openapi.json"; return info; } + + @GET + @Path("timing") + @Produces(MediaType.TEXT_PLAIN) + public String getTimeAnalysis() { + List times = DataManager.getInstance().getTiming().geStatistics(); + DataManager.getInstance().resetTiming(); + return times.stream().map(TimingStatistics::toString).collect(Collectors.joining("\n\n")); + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/crowdsourcing/CampaignItemResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/crowdsourcing/CampaignItemResource.java index e006cb24112..902e1b78d8d 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/crowdsourcing/CampaignItemResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/crowdsourcing/CampaignItemResource.java @@ -24,6 +24,7 @@ import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -40,6 +41,7 @@ import javax.ws.rs.core.MediaType; import org.apache.commons.lang3.StringUtils; +import org.apache.solr.common.SolrDocument; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,6 +59,8 @@ import io.goobi.viewer.controller.IndexerTools; import io.goobi.viewer.dao.IDAO; import io.goobi.viewer.exceptions.DAOException; +import io.goobi.viewer.exceptions.IndexUnreachableException; +import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.model.annotation.PersistentAnnotation; import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign; import io.goobi.viewer.model.crowdsourcing.campaigns.CampaignItem; @@ -140,10 +144,24 @@ public CampaignItem getItemForManifest(@PathParam("pi") String pi, @Context Http .map(clm -> new LogMessage(clm, servletRequest)) .collect(Collectors.toList())); } + try { + List allMetadataFields = campaign.getQuestions().stream().flatMap(q -> q.getMetadataFields().stream()).distinct().collect(Collectors.toList()); + SolrDocument doc = DataManager.getInstance().getSearchIndex().getFirstDoc("PI:" + pi, allMetadataFields); + Map> fieldValueMap = doc.getFieldNames().stream().collect(Collectors.toMap(field -> field, field -> getFieldValues(doc, field))); + item.setMetadata(fieldValueMap); + } catch (PresentationException | IndexUnreachableException e) { + logger.error("Error getting metadata valued for campaign item ", e); + } + + return item; } throw new ContentNotFoundException("No campaign found with id " + campaignId); } + + private List getFieldValues(SolrDocument doc, String field) { + return doc.getFieldValues(field).stream().map(Object::toString).collect(Collectors.toList()); + } /** * Sets the {@link io.goobi.viewer.model.crowdsourcing.campaigns.CampaignRecordStatistic.CampaignRecordStatus} for the given campaign and work and diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/index/IndexResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/index/IndexResource.java index b3a621bc45c..4e53380419d 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/index/IndexResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/index/IndexResource.java @@ -15,15 +15,15 @@ */ package io.goobi.viewer.api.rest.v1.index; -import static io.goobi.viewer.api.rest.v1.ApiUrls.*; +import static io.goobi.viewer.api.rest.v1.ApiUrls.INDEX; +import static io.goobi.viewer.api.rest.v1.ApiUrls.INDEX_QUERY; +import static io.goobi.viewer.api.rest.v1.ApiUrls.INDEX_STATISTICS; +import static io.goobi.viewer.api.rest.v1.ApiUrls.INDEX_STREAM; import java.io.IOException; -import java.io.OutputStream; -import java.net.MalformedURLException; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Random; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -53,9 +53,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import de.unigoettingen.sub.commons.contentlib.exceptions.ContentNotFoundException; import de.unigoettingen.sub.commons.contentlib.exceptions.IllegalRequestException; -import de.unigoettingen.sub.commons.contentlib.exceptions.ServiceNotAllowedException; import de.unigoettingen.sub.commons.contentlib.servlet.rest.CORSBinding; import io.goobi.viewer.api.rest.ViewerRestServiceBinding; import io.goobi.viewer.controller.DataManager; @@ -65,7 +63,6 @@ import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.exceptions.ViewerConfigurationException; -import io.goobi.viewer.model.misc.NumberIterator; import io.goobi.viewer.model.search.SearchHelper; import io.goobi.viewer.model.viewer.StringPair; import io.goobi.viewer.servlets.rest.content.RecordsRequestParameters; @@ -90,7 +87,6 @@ public class IndexResource { @Context private HttpServletResponse servletResponse; - @GET @Path(INDEX_STATISTICS) @Produces({ MediaType.APPLICATION_JSON }) @@ -98,14 +94,13 @@ public class IndexResource { tags = { "index" }, summary = "Statistics about indexed records") public String getStatistics( - @Parameter(description="SOLR Query to filter results (optional)") @QueryParam("query") String query) throws MalformedURLException, ContentNotFoundException, - ServiceNotAllowedException, IndexUnreachableException, PresentationException, ViewerConfigurationException, DAOException, - IllegalRequestException { - - if(query == null) { + @Parameter(description = "SOLR Query to filter results (optional)") @QueryParam("query") String query) + throws IndexUnreachableException, PresentationException { + + if (query == null) { query = "+(ISWORK:*) "; } - + String finalQuery = new StringBuilder().append(query).append(SearchHelper.getAllSuffixes(servletRequest, true, true)).toString(); logger.debug("q: {}", finalQuery); @@ -114,7 +109,7 @@ public String getStatistics( json.put("count", count); return json.toString(); } - + @POST @Path(INDEX_QUERY) @Consumes({ MediaType.APPLICATION_JSON }) @@ -123,19 +118,18 @@ public String getStatistics( tags = { "index" }, summary = "Post a query directly the SOLR index") @ApiResponse(responseCode = "400", description = "Illegal query or query parameters") - public String getRecordsForQuery(RecordsRequestParameters params) throws MalformedURLException, ContentNotFoundException, - ServiceNotAllowedException, IndexUnreachableException, PresentationException, ViewerConfigurationException, DAOException, - IllegalRequestException { + public String getRecordsForQuery(RecordsRequestParameters params) + throws IndexUnreachableException, ViewerConfigurationException, DAOException, IllegalRequestException { JSONObject ret = new JSONObject(); if (params == null || params.query == null) { ret.put("status", HttpServletResponse.SC_BAD_REQUEST); ret.put("message", "Invalid JSON request object"); return ret.toString(); } - + // Custom query does not filter by the sub-theme discriminator value by default, it has to be added to the custom query via #{navigationHelper.subThemeDiscriminatorValueSubQuery} -// String query = -// new StringBuilder().append("+").append(params.getQuery()).append(SearchHelper.getAllSuffixes(servletRequest, null, true, true, false)).toString(); + // String query = + // new StringBuilder().append("+").append(params.getQuery()).append(SearchHelper.getAllSuffixes(servletRequest, null, true, true, false)).toString(); String query = SearchHelper.buildFinalQuery(params.query, params.includeChildHits); logger.trace("query: {}", query); @@ -153,19 +147,18 @@ public String getRecordsForQuery(RecordsRequestParameters params) throws Malform } if (params.randomize) { sortFieldList.clear(); - // Solr supports dynamic random_* sorting fields. Each value represents one particular order, so a random number is required. - Random random = new Random(); - sortFieldList.add(new StringPair("random_" + random.nextInt(Integer.MAX_VALUE), ("desc".equals(params.sortOrder) ? "desc" : "asc"))); + sortFieldList.add(new StringPair(SolrSearchIndex.generateRandomSortField(), ("desc".equals(params.sortOrder) ? "desc" : "asc"))); } try { List fieldList = params.resultFields; - + Map paramMap = null; - if(params.includeChildHits) { + if (params.includeChildHits) { paramMap = SearchHelper.getExpandQueryParams(params.query); } -// QueryResponse response = DataManager.getInstance().getSearchIndex().search(query, params.offset, count, sortFieldList, null, fieldList ); - QueryResponse response = DataManager.getInstance().getSearchIndex().search(query, params.offset, count, sortFieldList, null, fieldList, null, paramMap ); + // QueryResponse response = DataManager.getInstance().getSearchIndex().search(query, params.offset, count, sortFieldList, null, fieldList ); + QueryResponse response = + DataManager.getInstance().getSearchIndex().search(query, params.offset, count, sortFieldList, null, fieldList, null, paramMap); SolrDocumentList result = response.getResults(); Map expanded = response.getExpandedResults(); @@ -192,7 +185,6 @@ public String getRecordsForQuery(RecordsRequestParameters params) throws Malform throw new IllegalRequestException(e.getMessage()); } } - @POST @Path(INDEX_STREAM) @@ -204,22 +196,22 @@ public String getRecordsForQuery(RecordsRequestParameters params) throws Malform @ApiResponse(responseCode = "400", description = "Illegal query or query parameters") @ApiResponse(responseCode = "500", description = "Solr not available or unable to respond") public StreamingOutput stream( - @Schema(description = "Raw SOLR streaming expression", example="search(collection1,q=\"+ISANCHOR:*\", sort=\"YEAR asc\", fl=\"YEAR,PI,DOCTYPE\", rows=5, qt=\"/select\")") - String expression) throws IndexUnreachableException { + @Schema(description = "Raw SOLR streaming expression", + example = "search(collection1,q=\"+ISANCHOR:*\", sort=\"YEAR asc\", fl=\"YEAR,PI,DOCTYPE\", rows=5, qt=\"/select\")") String expression) { String solrUrl = DataManager.getInstance().getSearchIndex().getSolrServerUrl(); logger.trace("Call solr " + solrUrl); logger.trace("Streaming expression " + expression); return executeStreamingExpression(expression, solrUrl); } - - private StreamingOutput executeStreamingExpression(String expr, String solrUrl) throws IndexUnreachableException { - return (out) -> { + + private static StreamingOutput executeStreamingExpression(String expr, String solrUrl) { + return (out) -> { ObjectMapper mapper = new ObjectMapper(); ModifiableSolrParams paramsLoc = new ModifiableSolrParams(); paramsLoc.set("expr", expr); paramsLoc.set("qt", "/stream"); // Note, the "/collection" below can be an alias. - try(TupleStream solrStream = new SolrStream(solrUrl, paramsLoc)) { + try (TupleStream solrStream = new SolrStream(solrUrl, paramsLoc)) { StreamContext context = new StreamContext(); solrStream.setStreamContext(context); solrStream.open(); @@ -229,9 +221,9 @@ private StreamingOutput executeStreamingExpression(String expr, String solrUrl) String json = mapper.writeValueAsString(tuple); out.write((json + "\n").getBytes()); out.flush(); - } while(!tuple.EOF); + } while (!tuple.EOF); } catch (IOException e) { - if(e.getMessage() != null && e.getMessage().contains("not a proper expression clause")) { + if (e.getMessage() != null && e.getMessage().contains("not a proper expression clause")) { throw new WebApplicationException(new IllegalRequestException(e.getMessage())); } throw new WebApplicationException(new IndexUnreachableException(e.toString())); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordFileResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordFileResource.java index 1fa2e0f43c1..598eafba524 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordFileResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordFileResource.java @@ -164,8 +164,7 @@ public StreamingOutput getSourceFile( throw new ContentNotFoundException("Source file " + filename + " not found"); } - boolean access = !AccessConditionUtils.checkContentFileAccessPermission(pi, servletRequest, Collections.singletonList(path)) - .containsValue(Boolean.FALSE); + boolean access = AccessConditionUtils.checkContentFileAccessPermission(pi, servletRequest); if (!access) { throw new ServiceNotAllowedException("Access to source file " + filename + " not allowed"); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordPageResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordPageResource.java index d0e83ae3bd0..84622ef58a6 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordPageResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordPageResource.java @@ -120,12 +120,14 @@ public DocumentReference getNERTags( @Operation(tags = { "records", "iiif" }, summary = "Get IIIF base sequence") @IIIFPresentationBinding public IPresentationModelElement getSequence(@Parameter( - description = "Build mode for manifes to select type of resources to include. Default is 'iiif' which returns the full IIIF manifest with all resources. 'thumbs' Does not read width and height of canvas resources and 'iiif_simple' ignores all resources from files") @QueryParam("mode") String mode) - throws ContentNotFoundException, PresentationException, IndexUnreachableException, URISyntaxException, + description = "Build mode for manifes to select type of resources to include. Default is 'iiif' which returns the full IIIF manifest with all resources. 'thumbs' Does not read width and height of canvas resources and 'iiif_simple' ignores all resources from files") @QueryParam("mode") String mode, + @Parameter(description = "Set prefered goobi-viewer view for rendering attribute of canvases. Only valid values is 'fullscreen', any other value results in default object/image view being referenced.") @QueryParam("preferedView") String preferedView) + + throws ContentNotFoundException, PresentationException, IndexUnreachableException, URISyntaxException, ViewerConfigurationException, DAOException, IllegalRequestException { IIIFPresentationResourceBuilder builder = new IIIFPresentationResourceBuilder(urls, servletRequest); BuildMode buildMode = RecordResource.getBuildeMode(mode); - Sequence sequence = builder.getBaseSequence(pi, buildMode); + Sequence sequence = builder.getBaseSequence(pi, buildMode, preferedView); return sequence; } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordsListResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordsListResource.java index a43f08e3b33..29c1b523e73 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordsListResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/api/rest/v1/records/RecordsListResource.java @@ -87,7 +87,7 @@ public OrderedCollectionPage listManifests( firstRow = firstRow == null ? 0 : firstRow; rows = rows == null ? DEFAULT_MAX_ROWS : rows; - logger.trace("rows: {}", rows); + // logger.trace("rows: {}", rows); String finalQuery = createQuery(query, start, end, subtheme); logger.trace("final query: {}", finalQuery); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/Configuration.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/Configuration.java index 895dde28541..a228e64b842 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/Configuration.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/Configuration.java @@ -67,6 +67,7 @@ import io.goobi.viewer.model.security.authentication.VuFindProvider; import io.goobi.viewer.model.security.authentication.XServiceProvider; import io.goobi.viewer.model.termbrowsing.BrowsingMenuFieldConfig; +import io.goobi.viewer.model.transkribus.TranskribusUtils; import io.goobi.viewer.model.viewer.DcSortingList; import io.goobi.viewer.model.viewer.PageType; import io.goobi.viewer.model.viewer.StringPair; @@ -683,6 +684,30 @@ public boolean isDisplaySidebarRssFeed() { return getLocalBoolean("sidebar.sidebarRssFeed[@display]", true); } + /** + *

+ * isOriginalContentDownload. + *

+ * + * @should return correct value + * @return a boolean. + */ + public boolean isDisplaySidebarWidgetDownload() { + return getLocalBoolean("sidebar.sidebarWidgetDownloads[@visible]", false); + } + + /** + *

+ * Returns a regex such that all download files which filenames fit this regex should not be visible in the downloads widget. If an empty string + * is returned, all downloads should remain visible + *

+ * + * @return a regex or an empty string if no downloads should be hidden + */ + public String getHideDownloadFileRegex() { + return getLocalString("sidebar.sidebarWidgetDownloads.hideFileRegex", ""); + } + /** *

* isDisplayWidgetUsage. @@ -859,7 +884,7 @@ public String getCollectionSplittingChar(String field) { return subConfig.getString("splittingCharacter", "."); } - return getLocalString("viewer.splittingCharacter", "."); + return getLocalString("collections.splittingCharacter", "."); } /** @@ -1374,7 +1399,7 @@ public List getDisplayAdditionalMetadataTranslateFields() { public boolean isAdvancedSearchFieldHierarchical(String field) { return isAdvancedSearchFieldHasAttribute(field, "hierarchical"); } - + /** *

* isAdvancedSearchFieldRange. @@ -1972,6 +1997,15 @@ public String getSmtpSenderName() { public String getSmtpSecurity() { return getLocalString("user.smtpSecurity", "none"); } + + /** + * + * @return Configured SMTP port number; -1 if not configured + * @should return correct value + */ + public int getSmtpPort() { + return getLocalInt("user.smtpPort", -1); + } /** *

@@ -2922,30 +2956,6 @@ public String getRssCopyrightText() { return getLocalString("rss.copyright"); } - /** - *

- * getRulesetFilePath. - *

- * - * @should return correct value - * @return a {@link java.lang.String} object. - */ - public String getRulesetFilePath() { - return getLocalString("content.ruleset"); - } - - /** - *

- * getDefaultCollection. - *

- * - * @should return correct value - * @return a {@link java.lang.String} object. - */ - public String getDefaultCollection() { - return getLocalString("content.defaultCollection"); - } - /** *

* getThumbnailsWidth. @@ -3420,18 +3430,6 @@ public String getWatermarkFormat() { return getLocalString("viewer.watermarkFormat", "jpg"); } - /** - *

- * isOriginalContentDownload. - *

- * - * @should return correct value - * @return a boolean. - */ - public boolean isOriginalContentDownload() { - return getLocalBoolean("content.originalContentDownload", false); - } - /** *

* getStopwordsFilePath. @@ -4815,4 +4813,25 @@ public List getLicenseDescriptions() { return licenses; } + /** + * @return + */ + public String getBaseXUrl() { + return getLocalString("urls.basex.url"); + } + + /** + * @return + */ + public String getBaseXDatabase() { + return getLocalString("urls.basex.defaultDatabase"); + + } + + /** + * @return + */ + public HierarchicalConfiguration getBaseXMetadataConfig() { + return getLocalConfigurationAt("metadata.basexMetadataList"); + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/DataManager.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/DataManager.java index 86c48e70c17..79885e5c4f3 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/DataManager.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/DataManager.java @@ -24,6 +24,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import de.intranda.monitoring.timer.TimeAnalysis; import io.goobi.viewer.controller.language.LanguageHelper; import io.goobi.viewer.dao.IDAO; import io.goobi.viewer.dao.impl.JPADAO; @@ -77,6 +78,8 @@ public final class DataManager { private String indexerVersion = ""; private RestApiManager restApiManager; + + private TimeAnalysis timing = new TimeAnalysis(); /** *

@@ -445,4 +448,19 @@ public void setRestApiManager(RestApiManager restApiManager) { public RecordLockManager getRecordLockManager() { return recordLockManager; } + + /** + * @return the timing + */ + public TimeAnalysis getTiming() { + return timing; + } + + /** + * + */ + public void resetTiming() { + this.timing = new TimeAnalysis(); + + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/JsonTools.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/JsonTools.java index 6e5a82e82f4..fb5c3c90dd9 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/JsonTools.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/JsonTools.java @@ -36,6 +36,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.goobi.viewer.controller.SolrConstants.DocType; +import io.goobi.viewer.controller.imaging.ThumbnailHandler; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; @@ -77,6 +78,7 @@ public static JSONArray getRecordJsonArray(SolrDocumentList result, Map recipients, String subject, String b return postMail(recipients, subject, body, DataManager.getInstance().getConfiguration().getSmtpServer(), DataManager.getInstance().getConfiguration().getSmtpUser(), DataManager.getInstance().getConfiguration().getSmtpPassword(), DataManager.getInstance().getConfiguration().getSmtpSenderAddress(), DataManager.getInstance().getConfiguration().getSmtpSenderName(), - DataManager.getInstance().getConfiguration().getSmtpSecurity()); + DataManager.getInstance().getConfiguration().getSmtpSecurity(), DataManager.getInstance().getConfiguration().getSmtpPort()); } /** @@ -245,11 +245,12 @@ public static boolean postMail(List recipients, String subject, String b * @param smtpSenderAddress * @param smtpSenderName * @param smtpSecurity + * @param smtpPort * @throws MessagingException * @throws UnsupportedEncodingException */ private static boolean postMail(List recipients, String subject, String body, String smtpServer, final String smtpUser, - final String smtpPassword, String smtpSenderAddress, String smtpSenderName, String smtpSecurity) + final String smtpPassword, String smtpSenderAddress, String smtpSenderName, String smtpSecurity, Integer smtpPort) throws MessagingException, UnsupportedEncodingException { if (recipients == null) { throw new IllegalArgumentException("recipients may not be null"); @@ -282,13 +283,15 @@ private static boolean postMail(List recipients, String subject, String if (StringUtils.isEmpty(smtpUser)) { auth = false; } - String security = DataManager.getInstance().getConfiguration().getSmtpSecurity(); Properties props = new Properties(); - switch (security.toUpperCase()) { + switch (smtpSecurity.toUpperCase()) { case "STARTTLS": logger.debug("Using STARTTLS"); + if (smtpPort == -1) { + smtpPort = 25; + } props.setProperty("mail.transport.protocol", "smtp"); - props.setProperty("mail.smtp.port", "25"); + props.setProperty("mail.smtp.port", String.valueOf(smtpPort)); props.setProperty("mail.smtp.host", smtpServer); props.setProperty("mail.smtp.ssl.trust", "*"); props.setProperty("mail.smtp.starttls.enable", "true"); @@ -296,21 +299,28 @@ private static boolean postMail(List recipients, String subject, String break; case "SSL": logger.debug("Using SSL"); + if (smtpPort == -1) { + smtpPort = 465; + } props.setProperty("mail.transport.protocol", "smtp"); props.setProperty("mail.smtp.host", smtpServer); - props.setProperty("mail.smtp.port", "465"); + props.setProperty("mail.smtp.port", String.valueOf(smtpPort)); props.setProperty("mail.smtp.ssl.enable", "true"); props.setProperty("mail.smtp.ssl.trust", "*"); break; default: logger.debug("Using no SMTP security"); + if (smtpPort == -1) { + smtpPort = 25; + } props.setProperty("mail.transport.protocol", "smtp"); - props.setProperty("mail.smtp.port", "25"); + props.setProperty("mail.smtp.port", String.valueOf(smtpPort)); props.setProperty("mail.smtp.host", smtpServer); } props.setProperty("mail.smtp.connectiontimeout", "15000"); props.setProperty("mail.smtp.timeout", "15000"); props.setProperty("mail.smtp.auth", String.valueOf(auth)); + logger.debug("Connecting to email server " + smtpServer + " on port " + String.valueOf(smtpPort) + " via SMTP security " + smtpSecurity.toUpperCase()); // logger.trace(props.toString()); Session session; diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/RestApiManager.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/RestApiManager.java index 0c4b6679693..1fded8cc3c2 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/RestApiManager.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/RestApiManager.java @@ -86,7 +86,9 @@ public RestApiManager(Configuration config) { * null is returned */ public AbstractApiUrlManager getDataApiManager() { - this.updateDataUrlManager(); + if(this.dataApiManager == null) { + this.updateDataUrlManager(); + } return dataApiManager; } @@ -121,7 +123,9 @@ public String getContentApiUrl() { * Otherwise null is returned */ public AbstractApiUrlManager getContentApiManager() { - this.updateContentUrlManager(); + if(this.contentApiManager == null) { + this.updateContentUrlManager(); + } return contentApiManager; } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrConstants.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrConstants.java index f852e143771..3f0a7097f78 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrConstants.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrConstants.java @@ -241,6 +241,8 @@ public static MetadataGroupType getByName(String name) { public static final String SUPERFULLTEXT = "SUPERFULLTEXT"; /** Constant SUPERUGCTERMS="SUPERUGCTERMS" */ public static final String SUPERUGCTERMS = "SUPERUGCTERMS"; + /** Constant TECTONICS_ID="MD_TECTONICS_ID" */ + public static final String ARCHIVE_ENTRY_ID = "MD_ARCHIVE_ENTRY_ID"; /** Constant TITLE="MD_TITLE" */ public static final String TITLE = "MD_TITLE"; /** Constant THUMBNAIL="THUMBNAIL" */ diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrSearchIndex.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrSearchIndex.java index e5649907c82..50745623b2d 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrSearchIndex.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/SolrSearchIndex.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Random; import java.util.Set; import java.util.function.BinaryOperator; import java.util.regex.Matcher; @@ -51,8 +52,10 @@ import org.apache.solr.client.solrj.response.LukeResponse; import org.apache.solr.client.solrj.response.LukeResponse.FieldInfo; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.SolrPingResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; +import org.apache.solr.common.SolrException; import org.apache.solr.common.luke.FieldFlag; import org.jdom2.Document; import org.jdom2.Element; @@ -64,6 +67,7 @@ import de.intranda.metadata.multilanguage.IMetadataValue; import de.intranda.metadata.multilanguage.MultiLanguageMetadataValue; import io.goobi.viewer.controller.SolrConstants.DocType; +import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.HTTPException; import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; @@ -99,6 +103,8 @@ public final class SolrSearchIndex { Map dataRepositoryNames = new HashMap<>(); private SolrClient client; + + private List solrFields = null; /** *

@@ -227,6 +233,10 @@ public QueryResponse search(String query, int first, int rows, List for (int i = 0; i < sortFields.size(); ++i) { StringPair sortField = sortFields.get(i); if (StringUtils.isNotEmpty(sortField.getOne())) { + // If RANDOM is used, generate a randomized sort field + if ("RANDOM".equals(sortField.getOne())) { + sortField.setOne(generateRandomSortField()); + } solrQuery.addSort(sortField.getOne(), "desc".equals(sortField.getTwo()) ? ORDER.desc : ORDER.asc); } } @@ -1190,6 +1200,20 @@ public QueryResponse searchFacetsAndStatistics(String query, List filter throws PresentationException, IndexUnreachableException { return searchFacetsAndStatistics(query, filterQueries, facetFields, facetMinCount, null, getFieldStatistics); } + + public boolean pingSolrIndex() { + if(client != null) { + try { + SolrPingResponse ping = client.ping(); + return ping.getStatus() < 400; + } catch (SolrException | SolrServerException | IOException e) { + logger.trace("Ping to solr failed " + e.toString()); + return false; + } + } else { + return false; + } + } /** * Returns facets for the given facet field list. No actual docs are returned since they aren't necessary. @@ -1378,25 +1402,33 @@ public static boolean isQuerySyntaxError(Exception e) { *

* * @return a {@link java.util.List} object. + * @throws DAOException * @throws org.apache.solr.client.solrj.SolrServerException if any. * @throws java.io.IOException if any. */ - public List getAllFieldNames() throws SolrServerException, IOException { - LukeRequest lukeRequest = new LukeRequest(); - lukeRequest.setNumTerms(0); - LukeResponse lukeResponse = lukeRequest.process(client); - Map fieldInfoMap = lukeResponse.getFieldInfo(); - - List list = new ArrayList<>(); - for (String name : fieldInfoMap.keySet()) { - FieldInfo info = fieldInfoMap.get(name); - if (info != null && info.getType() != null && (info.getType().toLowerCase().contains("string") - || info.getType().toLowerCase().contains("text") || info.getType().toLowerCase().contains("tlong"))) { - list.add(name); + public List getAllFieldNames() throws DAOException { + try { + if(this.solrFields == null) { + LukeRequest lukeRequest = new LukeRequest(); + lukeRequest.setNumTerms(0); + LukeResponse lukeResponse = lukeRequest.process(client); + Map fieldInfoMap = lukeResponse.getFieldInfo(); + + List list = new ArrayList<>(); + for (String name : fieldInfoMap.keySet()) { + FieldInfo info = fieldInfoMap.get(name); + if (info != null && info.getType() != null && (info.getType().toLowerCase().contains("string") + || info.getType().toLowerCase().contains("text") || info.getType().toLowerCase().contains("tlong"))) { + list.add(name); + } + } + this.solrFields = list; } + } catch(IllegalStateException | SolrServerException | IOException e) { + throw new DAOException("Failed to load SOLR field names: " + e.toString()); } - return list; + return this.solrFields; } /** @@ -1750,6 +1782,15 @@ public static String getProcessedConditions(String conditions) { return conditions.trim(); } + + /** + * Solr supports dynamic random_* sorting fields. Each value represents one particular order, so a random number is required. + * + * @return Randomized sorting field + */ + public static String generateRandomSortField() { + return "random_" + new Random().nextInt(Integer.MAX_VALUE); + } /** * diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/imaging/ThumbnailHandler.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/imaging/ThumbnailHandler.java index 719da43882d..aa6097a06c0 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/imaging/ThumbnailHandler.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/imaging/ThumbnailHandler.java @@ -27,6 +27,7 @@ import org.slf4j.LoggerFactory; import de.intranda.api.iiif.IIIFUrlResolver; +import de.intranda.monitoring.timer.Time; import de.unigoettingen.sub.commons.contentlib.imagelib.ImageFileFormat; import de.unigoettingen.sub.commons.contentlib.imagelib.ImageType.Colortype; import de.unigoettingen.sub.commons.contentlib.imagelib.transform.Region; @@ -142,8 +143,8 @@ public String getThumbnailUrl(PhysicalElement page) { * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ public String getThumbnailUrl(String pi) throws IndexUnreachableException, PresentationException, ViewerConfigurationException { - return getThumbnailUrl(pi, DataManager.getInstance().getConfiguration().getThumbnailsWidth(), - DataManager.getInstance().getConfiguration().getThumbnailsHeight()); + return getThumbnailUrl(pi, DataManager.getInstance().getConfiguration().getThumbnailsWidth(), + DataManager.getInstance().getConfiguration().getThumbnailsHeight()); } /** diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/converter/StringListConverter.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/converter/StringListConverter.java new file mode 100644 index 00000000000..266a7b52560 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/converter/StringListConverter.java @@ -0,0 +1,61 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.dao.converter; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import javax.persistence.AttributeConverter; +import javax.persistence.Converter; + +import org.apache.commons.lang3.StringUtils; + +/** + * Store simple strings in single database field + * + * @author florian + * + */ +@Converter +public class StringListConverter implements AttributeConverter, String> { + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToDatabaseColumn(java.lang.Object) + */ + @Override + public String convertToDatabaseColumn(List attribute) { + if(attribute == null || attribute.isEmpty()) { + return null; + } else { + return attribute.stream().map(s -> s.replace(",", ",")).collect(Collectors.joining(",")); + } + } + + /* (non-Javadoc) + * @see javax.persistence.AttributeConverter#convertToEntityAttribute(java.lang.Object) + */ + @Override + public List convertToEntityAttribute(String dbData) { + if(StringUtils.isBlank(dbData)) { + return new ArrayList<>(); + } else { + return Arrays.asList(dbData.split(",")).stream().map(s -> s.replace(",", ",")).collect(Collectors.toList()); + } + } + +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/impl/JPADAO.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/impl/JPADAO.java index fadfb2d6827..5be6153d93d 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/impl/JPADAO.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/dao/impl/JPADAO.java @@ -3508,7 +3508,7 @@ public Campaign getCampaign(Long id) throws DAOException { try { Campaign o = em.getReference(Campaign.class, id); if (o != null) { - // updateFromDatabase(id, Campaign.class); +// em.refresh(o); } return o; } catch (EntityNotFoundException e) { @@ -3597,7 +3597,7 @@ public boolean updateCampaign(Campaign campaign) throws DAOException { c.resetSolrQueryResults(); return true; } catch (RollbackException e) { - return false; + throw new PersistenceException("Failed to persist campaign " + campaign, e); } } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/BaseXException.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/BaseXException.java new file mode 100644 index 00000000000..3b577eea799 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/BaseXException.java @@ -0,0 +1,40 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.exceptions; + +import java.io.Serializable; + +/** + *

+ * IndexUnreachableException class. + *

+ */ +public class BaseXException extends Exception implements Serializable { + + private static final long serialVersionUID = -5840484445206784670L; + + /** + *

+ * Constructor for IndexUnreachableException. + *

+ * + * @param string {@link java.lang.String} + */ + public BaseXException(String string) { + super(string); + } + +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/MyExceptionHandler.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/MyExceptionHandler.java index a1af8c8fde5..6825b063602 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/MyExceptionHandler.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/exceptions/MyExceptionHandler.java @@ -208,7 +208,7 @@ public void handle() throws FacesException { i.remove(); } } else if (t instanceof DAOException || isCausedByExceptionType(t, DAOException.class.getName()) - || (t instanceof PrettyException && t.getMessage().contains(IndexUnreachableException.class.getSimpleName()))) { + || (t instanceof PrettyException && t.getMessage().contains(DAOException.class.getSimpleName()))) { logger.trace("Caused by DAOException"); try { requestMap.put("errorType", "dao"); @@ -218,7 +218,18 @@ public void handle() throws FacesException { } finally { i.remove(); } - } else if (t instanceof ViewerConfigurationException || isCausedByExceptionType(t, ViewerConfigurationException.class.getName()) + } else if (t instanceof BaseXException || isCausedByExceptionType(t, BaseXException.class.getName()) + || (t instanceof PrettyException && t.getMessage().contains(BaseXException.class.getSimpleName()))) { + logger.trace("Caused by BaseXException"); + try { + requestMap.put("errorType", "basex"); + flash.put("errorType", "basex"); + nav.handleNavigation(fc, null, "pretty:error"); + fc.renderResponse(); + } finally { + i.remove(); + } + }else if (t instanceof ViewerConfigurationException || isCausedByExceptionType(t, ViewerConfigurationException.class.getName()) || (t instanceof PrettyException && t.getMessage().contains(ViewerConfigurationException.class.getSimpleName()))) { logger.trace("Caused by ViewerConfigurationException"); String msg = getRootCause(t).getMessage(); @@ -267,7 +278,7 @@ public void handle() throws FacesException { requestMap.put("errMsg", msg); requestMap.put("errorType", "general"); flash.put("errorType", "general"); -// nav.handleNavigation(fc, null, "pretty:error"); + nav.handleNavigation(fc, null, "pretty:error"); fc.renderResponse(); } finally { i.remove(); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ActiveDocumentBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ActiveDocumentBean.java index f25fe65259d..b1722511ebe 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ActiveDocumentBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ActiveDocumentBean.java @@ -145,7 +145,7 @@ public class ActiveDocumentBean implements Serializable { private String selectedRecordLanguage; private Boolean deleteRecordKeepTrace; - + private CMSSidebarElement mapWidget = null; private int reloads = 0; @@ -1387,7 +1387,7 @@ public String getTitleBarLabel(String language) if (StringUtils.isNotEmpty(label)) { return label; } - } else if (cmsBean != null) { + } else if (cmsBean != null && navigationHelper.isCmsPage()) { CMSPage cmsPage = cmsBean.getCurrentPage(); if (cmsPage != null) { String cmsPageName = StringUtils.isNotBlank(cmsPage.getMenuTitle()) ? cmsPage.getMenuTitle() : cmsPage.getTitle(); @@ -1396,8 +1396,13 @@ public String getTitleBarLabel(String language) } } } - - return null; + + if(navigationHelper.getCurrentPageType() != null) { + PageType pageType = navigationHelper.getCurrentPageType(); + return Messages.translate(pageType.getName(), Locale.forLanguageTag(language)); + } else { + return null; + } } /** @@ -1888,18 +1893,20 @@ public void setDeleteRecordKeepTrace(Boolean deleteRecordKeepTrace) { } public CMSSidebarElement getMapWidget() throws PresentationException, DAOException { - if(this.mapWidget == null) { + if (this.mapWidget == null) { this.mapWidget = generateMapWidget(); } return this.mapWidget; } - public CMSSidebarElement generateMapWidget() throws PresentationException, DAOException { - CMSSidebarElement widget = new CMSSidebarElement(); widget.setType("widgetGeoMap"); try { + if ("-".equals(getPersistentIdentifier())) { + return null; + } + GeoMap map = new GeoMap(); map.setId(Long.MAX_VALUE); map.setType(GeoMapType.SOLR_QUERY); @@ -1907,7 +1914,7 @@ public CMSSidebarElement generateMapWidget() throws PresentationException, DAOEx map.setMarkerTitleField(null); map.setMarker("default"); map.setSolrQuery(String.format("PI:%s OR PI_TOPSTRUCT:%s", getPersistentIdentifier(), getPersistentIdentifier())); - + if (!map.getFeaturesAsString().equals("[]") || contentBean.hasGeoCoordinateAnnotations(getPersistentIdentifier())) { widget.setGeoMap(map); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AdminBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AdminBean.java index f3d71f92b24..d8f6f56a086 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AdminBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AdminBean.java @@ -547,7 +547,7 @@ public void saveUserRoleAction() throws DAOException { Messages.error("userGroup_memberAddFailure"); } } - setCurrentUserRole(null); + resetCurrentUserRoleAction(); } /** @@ -564,7 +564,7 @@ public void deleteUserRoleAction(UserRole userRole) throws DAOException { } else { Messages.error("deleteFailure"); } - setCurrentUserRole(null); + resetCurrentUserRoleAction(); } // LicenseType @@ -1688,7 +1688,7 @@ public List getPossibleAccessConditions() throws IndexUnreachableExcepti Collections.sort(accessConditions); return accessConditions; } - + /** * * @return List of access condition values that have no corresponding license type in the database diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AnnotationBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AnnotationBean.java new file mode 100644 index 00000000000..1d408ff5071 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/AnnotationBean.java @@ -0,0 +1,295 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.managedbeans; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.Serializable; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.enterprise.context.SessionScoped; +import javax.faces.context.ExternalContext; +import javax.faces.context.FacesContext; +import javax.faces.view.ViewScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.dao.IDAO; +import io.goobi.viewer.exceptions.DAOException; +import io.goobi.viewer.exceptions.IndexUnreachableException; +import io.goobi.viewer.exceptions.PresentationException; +import io.goobi.viewer.exceptions.ViewerConfigurationException; +import io.goobi.viewer.managedbeans.tabledata.TableDataProvider; +import io.goobi.viewer.managedbeans.tabledata.TableDataSource; +import io.goobi.viewer.managedbeans.tabledata.TableDataProvider.SortOrder; +import io.goobi.viewer.messages.Messages; +import io.goobi.viewer.model.annotation.PersistentAnnotation; +import io.goobi.viewer.model.annotation.export.AnnotationSheetWriter; +import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign; +import io.goobi.viewer.model.crowdsourcing.questions.Question; +import io.goobi.viewer.model.misc.SelectionManager; +import io.goobi.viewer.model.toc.export.pdf.TocWriter; +import io.goobi.viewer.model.toc.export.pdf.WriteTocException; + +/** + * @author florian + * + */ +@Named +@ViewScoped +public class AnnotationBean implements Serializable { + + private static final long serialVersionUID = 8377250065305331020L; + + private static final Logger logger = LoggerFactory.getLogger(AnnotationBean.class); + + private static final int DEFAULT_ROWS_PER_PAGE = 15; + + @Inject + protected CrowdsourcingBean crowdsourcingBean; + + private TableDataProvider lazyModelAnnotations; + + private SelectionManager exportSelection = new SelectionManager<>(); + + private String ownerCampaignId = ""; + + private String targetRecordPI = ""; + + + @PostConstruct + public void init() { + if (lazyModelAnnotations == null) { + lazyModelAnnotations = new TableDataProvider(new TableDataSource() { + + private Optional numCreatedPages = Optional.empty(); + + @Override + public List getEntries(int first, int pageSize, String sortField, SortOrder sortOrder, + Map filters) { + try { + if (StringUtils.isBlank(sortField)) { + sortField = "id"; + sortOrder = SortOrder.DESCENDING; + } + filters.putAll(getFilters()); + List ret = + DataManager.getInstance().getDao().getAnnotations(first, pageSize, sortField, sortOrder.asBoolean(), filters); + exportSelection = new SelectionManager(ret.stream().map(PersistentAnnotation::getId).collect(Collectors.toList())); + return ret; + } catch (DAOException e) { + logger.error("Could not initialize lazy model: {}", e.getMessage()); + } + + return Collections.emptyList(); + } + + /** + * @param filters + */ + public Map getFilters() { + Map filters = new HashMap<>(); + if (StringUtils.isNotEmpty(getOwnerCampaignId())) { + filters.put("generatorId", getOwnerCampaignId()); + } + if (StringUtils.isNotEmpty(getTargetRecordPI())) { + filters.put("targetPI", getTargetRecordPI()); + } + + return filters; + } + + @Override + public long getTotalNumberOfRecords(Map filters) { + if (!numCreatedPages.isPresent()) { + try { + filters.putAll(getFilters()); + numCreatedPages = Optional.ofNullable(DataManager.getInstance().getDao().getAnnotationCount(filters)); + } catch (DAOException e) { + logger.error("Unable to retrieve total number of campaigns", e); + } + } + return numCreatedPages.orElse(0l); + } + + @Override + public void resetTotalNumberOfRecords() { + numCreatedPages = Optional.empty(); + } + + + }); + lazyModelAnnotations.setEntriesPerPage(DEFAULT_ROWS_PER_PAGE); + lazyModelAnnotations.setFilters("targetPI_body"); + } + } + + /** + *

+ * Getter for the field lazyModelAnnotations. + *

+ * + * @return the lazyModelAnnotations + */ + public TableDataProvider getLazyModelAnnotations() { + return lazyModelAnnotations; + } + + /** + * @return the ownerCampaignId + */ + public String getOwnerCampaignId() { + return ownerCampaignId; + } + + /** + * @param ownerCampaignId the ownerCampaignId to set + */ + public void setOwnerCampaignId(String ownerCampaignId) { + this.ownerCampaignId = ownerCampaignId; + } + + /** + * @return the ownerRecordPI + */ + public String getTargetRecordPI() { + return targetRecordPI; + } + + /** + * @param ownerRecordPI the ownerRecordPI to set + */ + public void setTargetRecordPI(String targetRecordPI) { + this.targetRecordPI = targetRecordPI; + } + + /** + * @return the exportSelection + */ + public SelectionManager getExportSelection() { + return exportSelection; + } + + + /** + * Deletes given annotation. + * + * @param annotation a {@link io.goobi.viewer.model.annotation.PersistentAnnotation} object. + * @return empty string + * @throws io.goobi.viewer.exceptions.DAOException if any. + */ + public String deleteAnnotationAction(PersistentAnnotation annotation) throws DAOException { + if (annotation == null) { + return ""; + } + + try { + if (annotation.delete()) { + Messages.info("admin__crowdsoucing_annotation_deleteSuccess"); + crowdsourcingBean.getLazyModelCampaigns().update(); + } + } catch (ViewerConfigurationException e) { + logger.error(e.getMessage()); + Messages.error(e.getMessage()); + } + + return ""; + } + + + public Optional getOwningCampaign(PersistentAnnotation anno) { + try { + IDAO dao = DataManager.getInstance().getDao(); + if(anno.getGeneratorId() != null) { + Question question = dao.getQuestion(anno.getGeneratorId()); + if(question != null) { + return Optional.ofNullable(question.getOwner()); + } + } + } catch(DAOException e) { + logger.error(e.toString(), e); + } + return Optional.empty(); + } + + /** + * Setter for {@link SelectionManager#setSelectAll(boolean) exportSelection#setSelectAll(boolean)} + * is placed here to avoid jsf confusing it with setting a value of the map + * @param select + */ + public void setSelectAll(boolean select) { + this.exportSelection.setSelectAll(select); + } + + /** + * Getter for {@link SelectionManager#isSelectAll() exportSelection#isSelectAll()} + * is placed here to avoid jsf confusing it with getting a value of the map + * @param select + * @return always false to deselect the select all button when loading the page + */ + public boolean isSelectAll() { + return false;//this.exportSelection.isSelectAll(); + } + + /** + * Create an excel sheet and write it to download stream + * @throws IOException + */ + public void downloadSelectedAnnotations() throws IOException { + List selectedAnnos = this.exportSelection.getAllSelected().stream() + .map(this::getAnnotationById) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + logger.debug("Selected " + selectedAnnos.size() + " annotations for excel download"); + downloadAnnotations(selectedAnnos); + } + + public void downloadAnnotations(List annotations) throws IOException { + try { + String fileName = "annotations.xlsx"; + + FacesContext fc = FacesContext.getCurrentInstance(); + ExternalContext ec = fc.getExternalContext(); + ec.responseReset(); // Some JSF component library or some Filter might have set some headers in the buffer beforehand. We want to get rid of them, else it may collide. + ec.setResponseContentType("application/msexcel"); + ec.setResponseHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\""); + OutputStream os = ec.getResponseOutputStream(); + AnnotationSheetWriter writer = new AnnotationSheetWriter(); + writer.createExcelSheet(os, annotations); + fc.responseComplete(); // Important! Otherwise JSF will attempt to render the response which obviously will fail since it's already written with a file and closed. + } finally { + + } + } + + public Optional getAnnotationById(Long id) { + return this.lazyModelAnnotations.getPaginatorList().stream().filter(anno -> id.equals(anno.getId())).findFirst(); + } + +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ArchiveBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ArchiveBean.java new file mode 100644 index 00000000000..9a9672c68f1 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ArchiveBean.java @@ -0,0 +1,477 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.managedbeans; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.ListIterator; +import java.util.Optional; +import java.util.stream.Collectors; + +import javax.annotation.PostConstruct; +import javax.faces.view.ViewScoped; +import javax.inject.Inject; +import javax.inject.Named; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.http.client.ClientProtocolException; +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrDocumentList; +import org.jdom2.Document; +import org.jdom2.JDOMException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.intranda.monitoring.timer.Time; +import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.controller.SolrConstants; +import io.goobi.viewer.controller.SolrSearchIndex; +import io.goobi.viewer.exceptions.BaseXException; +import io.goobi.viewer.exceptions.HTTPException; +import io.goobi.viewer.exceptions.IndexUnreachableException; +import io.goobi.viewer.exceptions.PresentationException; +import io.goobi.viewer.model.archives.ArchiveEntry; +import io.goobi.viewer.model.archives.ArchiveResource; +import io.goobi.viewer.model.archives.ArchiveTree; +import io.goobi.viewer.model.archives.BasexEADParser; +import io.goobi.viewer.model.viewer.StringPair; + +@Named +@ViewScoped +public class ArchiveBean implements Serializable { + + private static final long serialVersionUID = -1755934299534933504L; + + private static final Logger logger = LoggerFactory.getLogger(ArchiveBean.class); + + private ArchiveTree archiveTree; + + private String searchString; + + private DatabaseState databaseState = DatabaseState.NOT_INITIALIZED; + + @Deprecated + @Inject + private PersistentStorageBean storage; + + // @Inject + // private FacesContext context; + + private static enum DatabaseState { + NOT_INITIALIZED, + VALID, + ERROR_NOT_CONFIGURED, + ERROR_NOT_REACHABLE, + ERROR_INVALID_FORMAT + } + + /** + * Empty constructor. + */ + public ArchiveBean() { + // the emptiness inside + } + + /** + * + */ + @PostConstruct + public void init() { + String basexUrl = DataManager.getInstance().getConfiguration().getBaseXUrl(); + String databaseName = DataManager.getInstance().getConfiguration().getBaseXDatabase(); + if (StringUtils.isNoneBlank(basexUrl, databaseName)) { + BasexEADParser eadParser = new BasexEADParser(basexUrl); + this.databaseState = loadDatabase(eadParser, databaseName); + } else { + this.databaseState = DatabaseState.ERROR_NOT_CONFIGURED; + } + } + + DatabaseState loadDatabase(BasexEADParser eadParser, String databaseName) { + if (eadParser == null) { + return DatabaseState.NOT_INITIALIZED; + } + + // String storageKey = databaseName + "@" + eadParser.getBasexUrl(); + // if(context.getExternalContext().getSessionMap().containsKey(storageKey)) { + // eadParser = new BasexEADParser((BasexEADParser)context.getExternalContext().getSessionMap().containsKey(storageKey)); + // } else { + HierarchicalConfiguration baseXMetadataConfig = DataManager.getInstance().getConfiguration().getBaseXMetadataConfig(); + try { + eadParser.readConfiguration(baseXMetadataConfig); + Document databaseDoc = eadParser.retrieveDatabaseDocument(databaseName); + ArchiveEntry rootElement = eadParser.loadDatabase(databaseName, databaseDoc); + this.archiveTree = loadTree(rootElement); + logger.info("Loaded EAD database: {}", databaseName); + return DatabaseState.VALID; + } catch (IOException | HTTPException e) { + logger.error("Error retrieving database {} from {}", databaseName, eadParser.getBasexUrl()); + return DatabaseState.ERROR_NOT_REACHABLE; + } catch (JDOMException | ConfigurationException e) { + logger.error("Error reading database {} from {}", databaseName, eadParser.getBasexUrl()); + return DatabaseState.ERROR_INVALID_FORMAT; + } + // context.getExternalContext().getSessionMap().put(storageKey, eadParser); + // } + + } + + /** + * @param eadParser + * @param databaseName + * @throws ClientProtocolException + * @throws IOException + * @throws HTTPException + * @throws IllegalStateException + * @throws JDOMException + * @deprecated Storing database in application seems ineffective since verifying that database is current takes about as much time as retriving + * the complete database + */ + @Deprecated + public void loadDatabaseAndStoreInApplicationScope(BasexEADParser eadParser, String databaseName) + throws ClientProtocolException, IOException, HTTPException, IllegalStateException, JDOMException { + try (Time t = DataManager.getInstance().getTiming().takeTime("loadDatabase")) { + Document databaseDoc = null; + String storageKey = databaseName + "@" + eadParser.getBasexUrl(); + List dbs; + try (Time t1 = DataManager.getInstance().getTiming().takeTime("getPossibleDatabases")) { + dbs = eadParser.getPossibleDatabases(); + } + ArchiveResource db = dbs.stream().filter(res -> res.getCombinedName().equals(databaseName)).findAny().orElse(null); + if (db == null) { + throw new IllegalStateException("Configured default database not found in " + eadParser.getBasexUrl()); + } else if (storage.contains(storageKey) && !storage.olderThan(storageKey, db.lastModified)) { + databaseDoc = (Document) storage.get(storageKey); + } else { + try (Time t2 = DataManager.getInstance().getTiming().takeTime("retrieveDatabaseDocument")) { + databaseDoc = eadParser.retrieveDatabaseDocument(databaseName); + storage.put(storageKey, databaseDoc); + } + } + HierarchicalConfiguration baseXMetadataConfig = DataManager.getInstance().getConfiguration().getBaseXMetadataConfig(); + try (Time t3 = DataManager.getInstance().getTiming().takeTime("eadParser.loadDatabase")) { + eadParser.readConfiguration(baseXMetadataConfig).loadDatabase(databaseName, databaseDoc); + } catch (ConfigurationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + /** + * + * @return actual root element of the document, even if it's not in the displayed tree + */ + public ArchiveEntry getTrueRoot() { + if (archiveTree == null) { + return null; + } + + return archiveTree.getRootElement(); + } + + /** + * + * @return + * @throws BaseXException + */ + public ArchiveTree getArchiveTree() throws BaseXException { + return archiveTree; + } + + /** + * @param rootElement + * @return + */ + ArchiveTree loadTree(ArchiveEntry rootElement) { + if (rootElement == null) { + logger.trace("Root not found, cannot load tree."); + return null; + } + + ArchiveTree ret = new ArchiveTree(); + ret.generate(rootElement); + if (ret.getSelectedEntry() == null) { + ret.setSelectedEntry(ret.getRootElement()); + } + // This should happen before the tree is expanded to the selected entry, otherwise the collapse level will be reset + ret.getTreeView(); + + return ret; + } + + /** + *

+ * expandEntry. + *

+ * + * @param entry a {@link io.goobi.viewer.model.toc.TOCElement} object. + */ + public void expandEntry(ArchiveEntry entry) { + logger.trace("expandEntry: {}", entry); + if (archiveTree == null) { + return; + } + synchronized (archiveTree) { + entry.expand(); + } + } + + /** + *

+ * collapseEntry. + *

+ * + * @param entry a {@link io.goobi.viewer.model.toc.TOCElement} object. + */ + public void collapseEntry(ArchiveEntry entry) { + logger.trace("collapseEntry: {}", entry); + if (archiveTree == null) { + return; + } + + synchronized (archiveTree) { + entry.collapse(); + } + } + + /** + * Returns the entry hierarchy from the root down to the entry with the given identifier. + * + * @param identifier Entry identifier + * @param List of entries An empty list if the identified node has no ancestors or doesn't exist + */ + public List getArchiveHierarchyForIdentifier(String identifier) { + if (StringUtils.isEmpty(identifier)) { + return Collections.emptyList(); + } + + if (archiveTree == null) { + logger.error("Archive not loaded"); + return Collections.emptyList(); + } + + ArchiveEntry entry = archiveTree.getEntryById(identifier); + if (entry == null) { + // return Collections.emptyList(); + return Collections.singletonList(getTrueRoot()); + } else if (getTrueRoot().equals(entry) || getTrueRoot().equals(entry.getParentNode())) { + return Collections.singletonList(entry); + } else { + return entry.getAncestors(false).stream().skip(1).collect(Collectors.toList()); + } + } + + /** + * + * @param entry + * @return + */ + public String selectEntryAction(ArchiveEntry entry) { + if (entry == null || archiveTree == null) { + return ""; + } + + archiveTree.setSelectedEntry(entry); + + return ""; + } + + /** + * + * @return + * @throws BaseXException + */ + public String searchAction() throws BaseXException { + logger.trace("searchAction: {}", searchString); + search(true, true); + + return ""; + } + + /** + * Executes search for searchString. + * + * @param resetSelectedEntry If true, selected entry will be set to null + * @param collapseAll If true, all elements will be collapsed before expanding path to search hits + * @throws BaseXException + */ + void search(boolean resetSelectedEntry, boolean collapseAll) throws BaseXException { + if (!isDatabaseValid()) { + logger.warn("Archive not loaded, cannot search."); + return; + } + + if (StringUtils.isEmpty(searchString)) { + archiveTree.resetSearch(); + archiveTree.resetCollapseLevel(archiveTree.getRootElement(), ArchiveTree.defaultCollapseLevel); + return; + } + + archiveTree.search(searchString); + List results = archiveTree.getFlatEntryList(); + if (results == null || results.isEmpty()) { + return; + } + logger.trace("result entries: {}", results.size()); + + if (resetSelectedEntry) { + setSelectedEntryId(null); + } + archiveTree.collapseAll(collapseAll); + for (ArchiveEntry entry : results) { + if (entry.isSearchHit()) { + entry.expandUp(); + } + } + } + + /** + * @return the searchString + */ + public String getSearchString() { + return searchString; + } + + /** + * @param searchString the searchString to set + */ + public void setSearchString(String searchString) { + logger.trace("setSearchString: {}", searchString); + this.searchString = searchString; + } + + /** + * + * @return + */ + public String getSelectedEntryId() { + if (archiveTree == null || archiveTree.getSelectedEntry() == null) { + return "-"; + } + + return archiveTree.getSelectedEntry().getId(); + } + + /** + * Setter for the URL parameter. Loads the entry that has the given ID. Loads the tree, if this is a new sessions. + * + * @param id Entry ID + * @throws BaseXException + */ + public void setSelectedEntryId(String id) throws BaseXException { + logger.trace("setSelectedEntryId: {}", id); + if (!isDatabaseValid()) { + return; + } + + // Select root element if no ID given + if (StringUtils.isBlank(id)) { + id = "-"; + } + if ("-".equals(id)) { + archiveTree.setSelectedEntry(null); + return; + } + // Requested entry is already selected + if (archiveTree.getSelectedEntry() != null && archiveTree.getSelectedEntry().getId().equals(id)) { + return; + } + + // Find entry with given ID in the tree + ArchiveEntry result = archiveTree.getEntryById(id); + if (result != null) { + archiveTree.setSelectedEntry(result); + result.expandUp(); + } else { + logger.debug("Entry not found: {}", id); + archiveTree.setSelectedEntry(archiveTree.getRootElement()); + } + + } + + public boolean isSearchActive() { + return StringUtils.isNotBlank(searchString); + } + + /** + * + * @return the {@link ArchiveEntry} to display in the metadata section of the archives view. Either {@link ArchiveTree#getSelectedEntry()} or + * {@link ArchiveTree#getRootElement()} if the former is null + */ + public ArchiveEntry getDisplayEntry() { + if (archiveTree == null) { + return null; + } + + return Optional.ofNullable(archiveTree.getSelectedEntry()).orElse(archiveTree.getRootElement()); + } + + /** + * In the list of archive document search hits, find the id of the entry just before the given one + * + * @param entry + * @param sortOrder 'asc' to get the preceding entry, 'desc' to get the succeeding one + * @return the neighboring entry id if it exists + * @throws PresentationException + * @throws IndexUnreachableException + */ + public Pair, Optional> findIndexedNeighbours(String entryId) + throws PresentationException, IndexUnreachableException { + String query = SolrConstants.ARCHIVE_ENTRY_ID + ":*"; + List sortFields = Collections.singletonList(new StringPair(SolrConstants.PI, "asc")); + List fieldList = Arrays.asList(SolrConstants.PI, SolrConstants.ARCHIVE_ENTRY_ID); + + SolrDocumentList docs = DataManager.getInstance() + .getSearchIndex() + .search(query, SolrSearchIndex.MAX_HITS, sortFields, fieldList); + Optional prev = Optional.empty(); + Optional next = Optional.empty(); + + ListIterator iter = docs.listIterator(); + while (iter.hasNext()) { + SolrDocument doc = iter.next(); + String id = SolrSearchIndex.getSingleFieldStringValue(doc, SolrConstants.ARCHIVE_ENTRY_ID); + if (id.equals(entryId)) { + if (iter.hasNext()) { + String nextId = SolrSearchIndex.getSingleFieldStringValue(iter.next(), SolrConstants.ARCHIVE_ENTRY_ID); + next = Optional.of(nextId); + } + break; + } + prev = Optional.of(id); + } + return Pair.of(prev, next); + } + + /** + * @return the databaseState + */ + public DatabaseState getDatabaseState() { + return databaseState; + } + + public boolean isDatabaseValid() { + return DatabaseState.VALID.equals(this.databaseState); + } + +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/BreadcrumbBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/BreadcrumbBean.java index c2065dd6a83..0ccbce518e1 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/BreadcrumbBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/BreadcrumbBean.java @@ -463,7 +463,7 @@ public List getBreadcrumbs() { flattenedLinks.add(labeledLink); } } - logger.trace("getBreadcrumbs: {}", flattenedLinks.toString()); + // logger.trace("getBreadcrumbs: {}", flattenedLinks.toString()); return flattenedLinks; } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CmsBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CmsBean.java index 919f2d0b5ee..f7dbcac3a0b 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CmsBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CmsBean.java @@ -31,6 +31,7 @@ import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.annotation.PostConstruct; import javax.enterprise.context.SessionScoped; @@ -150,6 +151,8 @@ public class CmsBean implements Serializable { private List solrSortFields = null; private List solrGroupFields = null; + private List luceneFields = null; + /** *

* init. @@ -1592,7 +1595,7 @@ public String cmsContextAction(boolean resetSearch) if (StringUtils.isNotBlank(searchBean.getExactSearchString().replace("-", ""))) { searchBean.setShowReducedSearchOptions(true); return searchAction(item); - } else if (item.isDisplayEmptySearchResults()) { + } else if (item.isDisplayEmptySearchResults() || StringUtils.isNotBlank(searchBean.getFacets().getCurrentFacetString())) { String searchString = StringUtils.isNotBlank(item.getSolrQuery().replace("-", "")) ? item.getSolrQuery() : ""; // searchBean.setSearchString(item.getSolrQuery()); searchBean.setExactSearchString(searchString); @@ -1771,7 +1774,7 @@ public String searchAction(CMSContentItem item) } boolean aggregateHits = DataManager.getInstance().getConfiguration().isAggregateHits(); if (item != null && CMSContentItemType.SEARCH.equals(item.getType())) { - ((SearchFunctionality) item.getFunctionality()).search(); + ((SearchFunctionality) item.getFunctionality()).search(item.getOwnerPageLanguageVersion().getOwnerPage().getSubThemeDiscriminatorValue()); } else if (item != null && StringUtils.isNotBlank(item.getSolrQuery())) { Search search = new Search(SearchHelper.SEARCH_TYPE_REGULAR, SearchHelper.SEARCH_FILTER_ALL); search.setQuery(item.getSolrQuery()); @@ -2066,7 +2069,7 @@ public CollectionView getCollection(CMSPage page) throws PresentationException, * * @return a {@link java.util.List} object. */ - public static List getLuceneFields() { + public List getLuceneFields() { return getLuceneFields(false, false); } @@ -2078,7 +2081,7 @@ public static List getLuceneFields() { * @param includeUntokenized a boolean. * @return a {@link java.util.List} object. */ - public static List getLuceneFields(boolean includeUntokenized) { + public List getLuceneFields(boolean includeUntokenized) { return getLuceneFields(includeUntokenized, false); } @@ -2091,22 +2094,24 @@ public static List getLuceneFields(boolean includeUntokenized) { * @param excludeTokenizedMetadataFields a boolean. * @return a {@link java.util.List} object. */ - public static List getLuceneFields(boolean includeUntokenized, boolean excludeTokenizedMetadataFields) { - List constants; + public List getLuceneFields(boolean includeUntokenized, boolean excludeTokenizedMetadataFields) { try { - constants = DataManager.getInstance().getSearchIndex().getAllFieldNames(); - Iterator iterator = constants.iterator(); - while (iterator.hasNext()) { - String name = iterator.next(); - if (name.startsWith("_") || name.startsWith("FACET_") || name.startsWith("NORM_") - || (!includeUntokenized && name.endsWith(SolrConstants._UNTOKENIZED)) - || (excludeTokenizedMetadataFields && name.startsWith("MD_") && !name.endsWith(SolrConstants._UNTOKENIZED))) { - iterator.remove(); - } + if(this.luceneFields == null) { + this.luceneFields = DataManager.getInstance().getSearchIndex().getAllFieldNames(); + } + + Stream filteredLuceneFields = this.luceneFields.stream() + .filter(name -> !(name.startsWith("_") || name.startsWith("FACET_") || name.startsWith("NORM_"))); + if(!includeUntokenized) { + filteredLuceneFields = filteredLuceneFields.filter(name -> !name.endsWith(SolrConstants._UNTOKENIZED)); } - Collections.sort(constants); - return constants; - } catch (SolrServerException | IOException e) { + if(excludeTokenizedMetadataFields) { + filteredLuceneFields = filteredLuceneFields.filter(name -> !(name.startsWith("MD_") && !name.endsWith(SolrConstants._UNTOKENIZED))); + } + filteredLuceneFields = filteredLuceneFields.sorted(); + return filteredLuceneFields.collect(Collectors.toList()); + + } catch (DAOException e) { logger.error("Error retrieving solr fields", e); return Collections.singletonList(""); } @@ -2577,20 +2582,13 @@ public List getPossibleSortFields() throws SolrServerException, IOExcept * @throws java.io.IOException if any. */ public List getPossibleGroupFields() throws SolrServerException, IOException { + + if (this.solrGroupFields == null) { + this.solrGroupFields = DataManager.getInstance().getSearchIndex().getAllGroupFieldNames(); + Collections.sort(solrGroupFields); + } + return this.solrGroupFields; - if (this.solrGroupFields == null) { - this.solrGroupFields = DataManager.getInstance().getSearchIndex().getAllGroupFieldNames(); - Collections.sort(solrGroupFields); - } - return this.solrGroupFields; - - // List fields = new ArrayList<>(); - // fields.add(SolrConstants.SORTNUM_YEAR); - // fields.add(SolrConstants.DOCSTRCT); - // fields.add(SolrConstants.DC); - // fields.add(SolrConstants.PI_ANCHOR); - // fields.add(DataManager.getInstance().getConfiguration().getSubthemeDiscriminatorField()); - // return fields; } /** diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ConfigurationBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ConfigurationBean.java index a77e9e942b1..e8946d60795 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ConfigurationBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ConfigurationBean.java @@ -894,6 +894,7 @@ public boolean isDisplayTimeMatrix() { public boolean isDisplayCrowdsourcingModuleLinks() { return DataManager.getInstance().getConfiguration().isDisplayCrowdsourcingModuleLinks(); } + /** *

@@ -921,6 +922,7 @@ public int getTimeMatrixStartYear(String subTheme) throws PresentationException, } } + /** *

* getTimeMatrixEndYear. @@ -1280,7 +1282,7 @@ public String getRestApiUrl() throws ViewerConfigurationException { * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ public String getIiifApiUrl() throws ViewerConfigurationException { - return DataManager.getInstance().getConfiguration().getRestApiUrl(); + return DataManager.getInstance().getConfiguration().getIIIFApiUrl(); } /** @@ -1492,4 +1494,12 @@ public List getSearchHitsPerPageValues() { public boolean isAnonymousUserEmailAddressValid() { return EmailValidator.validateEmailAddress(DataManager.getInstance().getConfiguration().getAnonymousUserEmailAddress()); } + + /** + * + * @return true if default sorting field is 'RANDOM'; false otherwise + */ + public boolean isDefaultSortFieldRandom() { + return "RANDOM".equals(DataManager.getInstance().getConfiguration().getDefaultSortField()); + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CrowdsourcingBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CrowdsourcingBean.java index d573574b5ff..9aabe79edad 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CrowdsourcingBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/CrowdsourcingBean.java @@ -19,21 +19,22 @@ import java.io.Serializable; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.EnumSet; import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.enterprise.context.SessionScoped; -import javax.faces.context.FacesContext; import javax.inject.Inject; import javax.inject.Named; +import javax.persistence.PersistenceException; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang3.StringUtils; @@ -65,8 +66,10 @@ import io.goobi.viewer.model.crowdsourcing.CrowdsourcingTools; import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign; import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign.CampaignVisibility; +import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign.ReviewMode; import io.goobi.viewer.model.crowdsourcing.campaigns.CampaignRecordStatistic.CampaignRecordStatus; import io.goobi.viewer.model.crowdsourcing.questions.Question; +import io.goobi.viewer.model.misc.IPolyglott; import io.goobi.viewer.model.security.License; import io.goobi.viewer.model.security.LicenseType; import io.goobi.viewer.model.security.user.User; @@ -93,7 +96,6 @@ public class CrowdsourcingBean implements Serializable { protected UserBean userBean; private TableDataProvider lazyModelCampaigns; - private TableDataProvider lazyModelAnnotations; /** * The campaign selected in backend @@ -162,57 +164,6 @@ public void resetTotalNumberOfRecords() { lazyModelCampaigns.setFilters("name"); } - if (lazyModelAnnotations == null) { - lazyModelAnnotations = new TableDataProvider<>(new TableDataSource() { - - private Optional numCreatedPages = Optional.empty(); - - @Override - public List getEntries(int first, int pageSize, String sortField, SortOrder sortOrder, - Map filters) { - try { - if (StringUtils.isBlank(sortField)) { - sortField = "id"; - sortOrder = SortOrder.DESCENDING; - } - // Permanent filtering for annotations for a specific campaign - if (StringUtils.isNotEmpty(getTargetCampaignId())) { - filters.put("generatorId", getTargetCampaignId()); - } - List ret = - DataManager.getInstance().getDao().getAnnotations(first, pageSize, sortField, sortOrder.asBoolean(), filters); - return ret; - } catch (DAOException e) { - logger.error("Could not initialize lazy model: {}", e.getMessage()); - } - - return Collections.emptyList(); - } - - @Override - public long getTotalNumberOfRecords(Map filters) { - if (!numCreatedPages.isPresent()) { - try { - // Permanent filtering for annotations for a specific campaign - if (StringUtils.isNotEmpty(getTargetCampaignId())) { - filters.put("generatorId", getTargetCampaignId()); - } - numCreatedPages = Optional.ofNullable(DataManager.getInstance().getDao().getAnnotationCount(filters)); - } catch (DAOException e) { - logger.error("Unable to retrieve total number of campaigns", e); - } - } - return numCreatedPages.orElse(0l); - } - - @Override - public void resetTotalNumberOfRecords() { - numCreatedPages = Optional.empty(); - } - }); - lazyModelAnnotations.setEntriesPerPage(DEFAULT_ROWS_PER_PAGE); - lazyModelAnnotations.setFilters("targetPI_body"); - } } /** @@ -257,19 +208,8 @@ public String filterCampaignsAction(CampaignVisibility visibility) { * * @return A list of all locales supported by this viewer application */ - public static List getAllLocales() { - List list = new LinkedList<>(); - list.add(ViewerResourceBundle.getDefaultLocale()); - if (FacesContext.getCurrentInstance() != null && FacesContext.getCurrentInstance().getApplication() != null) { - Iterator iter = FacesContext.getCurrentInstance().getApplication().getSupportedLocales(); - while (iter.hasNext()) { - Locale locale = iter.next(); - if (!list.contains(locale)) { - list.add(locale); - } - } - } - return list; + public static Collection getAllLocales() { + return IPolyglott.getLocalesStatic(); } /** @@ -456,16 +396,14 @@ public boolean isUserOwnsAnyCampaigns(User user) throws DAOException { public static boolean isAllowed(User user, Campaign campaign) throws DAOException { if (campaign == null) { return false; - } - - if (CampaignVisibility.PUBLIC.equals(campaign.getVisibility())) { - return true; - } - + } // Skip inactive campaigns if (!campaign.isHasStarted() || campaign.isHasEnded()) { return false; } + if (CampaignVisibility.PUBLIC.equals(campaign.getVisibility())) { + return true; + } // Allow campaigns with a set time frame, but no user group if (campaign.isTimePeriodEnabled() && campaign.getDateStart() != null && campaign.getDateEnd() != null @@ -475,15 +413,15 @@ public static boolean isAllowed(User user, Campaign campaign) throws DAOExceptio switch (campaign.getVisibility()) { case PRIVATE: - // Only logged in members may access campaigns limited to a user group - if (!campaign.isLimitToGroup() || campaign.getUserGroup() == null) { - return false; - } if (user == null) { return false; } - if (user.isSuperuser()) { + if ( user.isSuperuser()) { return true; + } + // Only logged in members may access campaigns limited to a user group + if (!campaign.isLimitToGroup() || campaign.getUserGroup() == null) { + return false; } try { return campaign.getUserGroup().getMembersAndOwner().contains(user); @@ -553,7 +491,12 @@ public String saveSelectedCampaignAction() throws DAOException, PresentationExce } selectedCampaign.setDateUpdated(now); if (selectedCampaign.getId() != null) { - success = DataManager.getInstance().getDao().updateCampaign(selectedCampaign); + try { + success = DataManager.getInstance().getDao().updateCampaign(selectedCampaign); + } catch(PersistenceException e) { + logger.error("Updating campaign " + selectedCampaign + " in database failed ", e); + success = false; + } } else { success = DataManager.getInstance().getDao().addCampaign(selectedCampaign); } @@ -592,42 +535,6 @@ public TableDataProvider getLazyModelCampaigns() { return lazyModelCampaigns; } - /** - *

- * Getter for the field lazyModelAnnotations. - *

- * - * @return the lazyModelAnnotations - */ - public TableDataProvider getLazyModelAnnotations() { - return lazyModelAnnotations; - } - - /** - * Deletes given annotation. - * - * @param annotation a {@link io.goobi.viewer.model.annotation.PersistentAnnotation} object. - * @return empty string - * @throws io.goobi.viewer.exceptions.DAOException if any. - */ - public String deleteAnnotationAction(PersistentAnnotation annotation) throws DAOException { - if (annotation == null) { - return ""; - } - - try { - if (annotation.delete()) { - Messages.info("admin__crowdsoucing_annotation_deleteSuccess"); - lazyModelCampaigns.update(); - } - } catch (ViewerConfigurationException e) { - logger.error(e.getMessage()); - Messages.error(e.getMessage()); - } - - return ""; - } - /** *

* Getter for the field selectedCampaign. @@ -1060,4 +967,10 @@ public void updateActiveCampaigns() throws DAOException, PresentationException, } logger.trace("Added {} identifiers to the map.", DataManager.getInstance().getRecordCampaignMap().size()); } + + public Set getPossibleReviewModes() { + return EnumSet.allOf(ReviewMode.class); + } + + } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ImageDeliveryBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ImageDeliveryBean.java index 728f8e1ab26..3b3fbd12f71 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ImageDeliveryBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/ImageDeliveryBean.java @@ -99,15 +99,15 @@ public class ImageDeliveryBean implements Serializable { @PostConstruct private void init() { + logger.trace("init"); try { Configuration config = DataManager.getInstance().getConfiguration(); - this.servletPath = getServletPathFromContext(); init(config); } catch (NullPointerException e) { logger.error("Failed to initialize ImageDeliveryBean: Resources misssing"); } } - + private String getServletPathFromContext() { String path; if (servletRequest != null) { @@ -128,9 +128,10 @@ private String getServletPathFromContext() { * @param apiUrls a {@link java.lang.String} object. */ public void init(Configuration config) { + this.servletPath = getServletPathFromContext(); AbstractApiUrlManager dataUrlManager = DataManager.getInstance().getRestApiManager().getDataApiManager(); AbstractApiUrlManager contentUrlManager = DataManager.getInstance().getRestApiManager().getContentApiManager(); - + this.staticImagesURI = getStaticImagesPath(this.servletPath, config.getTheme()); this.cmsMediaPath = config.getViewerHome() + config.getCmsMediaFolder(); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/NavigationHelper.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/NavigationHelper.java index fee1fa30a1c..5d7c0236ded 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/NavigationHelper.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/NavigationHelper.java @@ -115,7 +115,7 @@ public class NavigationHelper implements Serializable { /** Map for setting any navigation status variables. Replaces currentView, etc. */ protected Map statusMap = new HashMap<>(); - private String theme = ""; + private final String theme; /** Currently selected page from the main navigation bar. */ private String currentPage = "index"; @@ -126,7 +126,7 @@ public class NavigationHelper implements Serializable { * Empty constructor. */ public NavigationHelper() { - // the emptiness inside + theme = DataManager.getInstance().getConfiguration().getTheme(); } /** @@ -142,7 +142,6 @@ public void init() { locale = Locale.GERMAN; logger.warn("Could not access FacesContext, locale set to DE."); } - theme = DataManager.getInstance().getConfiguration().getTheme(); statusMap.put(KEY_CURRENT_PARTNER_PAGE, ""); statusMap.put(KEY_SELECTED_NEWS_ARTICLE, ""); statusMap.put(KEY_MENU_PAGE, "user"); @@ -248,7 +247,7 @@ public void setCurrentPage(String currentPage) { * @param resetCurrentDocument a boolean. */ public void setCurrentPage(String currentPage, boolean resetBreadcrubs, boolean resetCurrentDocument) { - logger.trace("setCurrentPage: {}", currentPage); + // logger.trace("setCurrentPage: {}", currentPage); setCurrentPage(currentPage, resetBreadcrubs, resetCurrentDocument, false); } @@ -1033,21 +1032,10 @@ public void resetTheme() { logger.trace("resetTheme"); // Resetting the current page here would result in the current record being flushed, which is bad for CMS overview pages // resetCurrentPage(); - theme = DataManager.getInstance().getConfiguration().getTheme(); setCmsPage(false); setSubThemeDiscriminatorValue(""); } - /** - *

- * Setter for the field theme. - *

- * - * @param theme a {@link java.lang.String} object. - */ - public void setTheme(String theme) { - this.theme = theme; - } /** *

@@ -1091,6 +1079,11 @@ public String getObjectUrl() { public String getImageUrl() { return BeanUtils.getServletPathWithHostAsUrlFromJsfContext() + "/" + PageType.viewImage.getName(); } + + public String getCurrentPageTypeUrl() { + return BeanUtils.getServletPathWithHostAsUrlFromJsfContext() + "/" + getCurrentPageType().getName(); + + } /** *

diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/PersistentStorageBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/PersistentStorageBean.java new file mode 100644 index 00000000000..eba3e1c4cf3 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/PersistentStorageBean.java @@ -0,0 +1,58 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.managedbeans; + +import java.io.Serializable; +import java.time.Instant; +import java.util.HashMap; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Named; + +import org.apache.commons.lang3.tuple.Pair; + +/** + * Used for application wide storage of objects accessible to other managed objects + * + * @author florian + * + */ +@Named +@ApplicationScoped +public class PersistentStorageBean implements Serializable { + + private static final long serialVersionUID = -5127431137772735598L; + + private Map> map = new HashMap<>(); + + + public synchronized Object get(String key) { + return map.get(key).getLeft(); + } + + public synchronized boolean olderThan(String key, Instant now) { + return map.get(key).getRight().isBefore(now); + } + + public synchronized Object put(String key, Object object) { + return map.put(key, Pair.of(object, Instant.now())); + } + + public boolean contains(String key) { + return map.containsKey(key); + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/SearchBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/SearchBean.java index 2b327c74907..1de7ecacf43 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/SearchBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/SearchBean.java @@ -2510,5 +2510,23 @@ public String getBookmarkListSharedKey() { .orElse(""); return value.replace("KEY::", ""); } + + public String searchInRecord(String queryField, String queryValue) { + + this.getAdvancedQueryGroups().get(0).getQueryItems().get(0).setField(queryField); + if(StringUtils.isNotBlank(queryValue)) { + this.getAdvancedQueryGroups().get(0).getQueryItems().get(0).setValue(queryValue); + } + this.getAdvancedQueryGroups().get(0).getQueryItems().get(0).setOperator(SearchItemOperator.IS); + this.getAdvancedQueryGroups().get(0).getQueryItems().get(1).setField("searchAdvanced_allFields"); + this.getAdvancedQueryGroups().get(0).getQueryItems().get(1).setOperator(SearchItemOperator.AUTO); + this.setActiveSearchType(1); + + return this.searchAdvanced(); + } + + public boolean isSolrIndexReachable() { + return DataManager.getInstance().getSearchIndex().pingSolrIndex(); + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/TermsOfUseBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/TermsOfUseBean.java index 49aa0b0043f..907d96d8957 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/TermsOfUseBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/TermsOfUseBean.java @@ -95,6 +95,9 @@ public void setActivated(boolean active) { } public boolean isActivated() { + if (termsOfUse == null) { + return false; + } return this.termsOfUse.isActive(); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/UserBean.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/UserBean.java index 32ad11234d5..50cdaaba5b7 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/UserBean.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/UserBean.java @@ -51,7 +51,6 @@ import io.goobi.viewer.controller.DataManager; import io.goobi.viewer.controller.NetTools; import io.goobi.viewer.controller.StringTools; -import io.goobi.viewer.controller.TranskribusUtils; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.HTTPException; import io.goobi.viewer.exceptions.IndexUnreachableException; @@ -70,6 +69,7 @@ import io.goobi.viewer.model.security.authentication.LoginResult; import io.goobi.viewer.model.security.user.User; import io.goobi.viewer.model.security.user.UserGroup; +import io.goobi.viewer.model.transkribus.TranskribusUtils; import io.goobi.viewer.model.urlresolution.ViewHistory; import io.goobi.viewer.model.urlresolution.ViewerPath; import io.goobi.viewer.model.viewer.Feedback; diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/tabledata/TableDataProvider.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/tabledata/TableDataProvider.java index 0c1039e2e88..e709a9ab316 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/tabledata/TableDataProvider.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/tabledata/TableDataProvider.java @@ -604,6 +604,7 @@ public void setFilters(String... columns) { */ void resetTotalNumberOfRecords() { source.resetTotalNumberOfRecords(); + resetCurrentList(); } /** diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/utils/BeanUtils.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/utils/BeanUtils.java index 0a771017111..fda2f16b8f3 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/utils/BeanUtils.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/managedbeans/utils/BeanUtils.java @@ -50,6 +50,7 @@ import io.goobi.viewer.managedbeans.MetadataBean; import io.goobi.viewer.managedbeans.NavigationHelper; import io.goobi.viewer.managedbeans.SearchBean; +import io.goobi.viewer.managedbeans.ArchiveBean; import io.goobi.viewer.managedbeans.UserBean; import io.goobi.viewer.model.security.user.User; import io.goobi.viewer.servlets.utils.ServletUtils; diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/PersistentAnnotation.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/PersistentAnnotation.java index 9bbf6819e1e..0ec5bf81171 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/PersistentAnnotation.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/PersistentAnnotation.java @@ -21,6 +21,7 @@ import java.nio.file.Paths; import java.time.LocalDateTime; import java.util.HashSet; +import java.util.Optional; import java.util.Set; import javax.persistence.Column; @@ -50,6 +51,8 @@ import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.exceptions.RecordNotFoundException; import io.goobi.viewer.exceptions.ViewerConfigurationException; +import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign; +import io.goobi.viewer.model.crowdsourcing.campaigns.CampaignRecordStatistic.CampaignRecordStatus; import io.goobi.viewer.model.crowdsourcing.questions.Question; import io.goobi.viewer.model.security.user.User; @@ -69,10 +72,10 @@ public class PersistentAnnotation { @Column(name = "annotation_id") private Long id; - @Column(name = "date_created") + @Column(name = "date_created", columnDefinition = "TIMESTAMP") private LocalDateTime dateCreated; - @Column(name = "date_modified") + @Column(name = "date_modified", columnDefinition = "TIMESTAMP") private LocalDateTime dateModified; @Column(name = "motivation") @@ -636,4 +639,30 @@ public String getAccessCondition() { public void setAccessCondition(String accessCondition) { this.accessCondition = accessCondition; } + + /** + * Find the record status of the generator campaign and pi. If the annotation does not belong to a campaign, return {@link CampaignRecordStatus.FINISHED} + * + * @return The review status for this annotation + * @throws DAOException + */ + public CampaignRecordStatus getReviewStatus() throws DAOException { + return Optional.ofNullable(getGenerator()).map(Question::getOwner).map(c -> c.getRecordStatus(getTargetPI())).orElse(CampaignRecordStatus.FINISHED); + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + StringBuilder sb = new StringBuilder("Crowdsourcing Annotation"); + sb.append("\n\t").append("Body:").append(getBody()); + sb.append("\n\t").append("Target:").append(getTarget()); + sb.append("\n\t").append("GeneratorId:").append(getGeneratorId()); + sb.append("\n\t").append("CreatorId:").append(getCreatorId()); + sb.append("\n\t").append("ReviewerId:").append(getReviewerId()); + + return sb.toString(); + + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/export/AnnotationSheetWriter.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/export/AnnotationSheetWriter.java new file mode 100644 index 00000000000..6ee30ac9b7d --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/export/AnnotationSheetWriter.java @@ -0,0 +1,73 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.model.annotation.export; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import de.intranda.api.annotation.IResource; +import de.intranda.api.annotation.wa.TypedResource; +import io.goobi.viewer.api.rest.resourcebuilders.AnnotationsResourceBuilder; +import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.model.annotation.PersistentAnnotation; + +/** + * @author florian + * + */ +public class AnnotationSheetWriter { + + public static final String UNKNOWN_RESOURCE_TYPE = "Unknown"; + + private final ExcelRenderer excelRenderer; + private final AnnotationsResourceBuilder annotationBuilder = new AnnotationsResourceBuilder(DataManager.getInstance().getRestApiManager().getDataApiManager(), null); + + public AnnotationSheetWriter() { + this.excelRenderer = new ExcelRenderer(annotationBuilder); + } + + /** + * @param os + * @param annotations + * @throws IOException + */ + public void createExcelSheet(OutputStream os, List annotations) throws IOException { + + Map> annoMap = annotations.stream().collect(Collectors.groupingBy(this::getBodyType)); + + HSSFWorkbook wb = excelRenderer.render(annoMap); + wb.write(os); + os.flush(); + } + + private String getBodyType(PersistentAnnotation anno) { + try { + IResource body = annotationBuilder.getBodyAsResource(anno); + if(body instanceof TypedResource) { + return ((TypedResource) body).getType(); + } + } catch (IOException e) { + } + return UNKNOWN_RESOURCE_TYPE; + } + +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/export/ExcelRenderer.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/export/ExcelRenderer.java new file mode 100644 index 00000000000..a2211353209 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/annotation/export/ExcelRenderer.java @@ -0,0 +1,225 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.model.annotation.export; + +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; +import java.util.Optional; + +import org.apache.poi.hssf.usermodel.HSSFCell; +import org.apache.poi.hssf.usermodel.HSSFCellStyle; +import org.apache.poi.hssf.usermodel.HSSFFont; +import org.apache.poi.hssf.usermodel.HSSFRow; +import org.apache.poi.hssf.usermodel.HSSFSheet; +import org.apache.poi.hssf.usermodel.HSSFWorkbook; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.CellType; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.Row; +import org.bouncycastle.asn1.cmc.BodyPartID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonParseException; +import com.fasterxml.jackson.databind.JsonMappingException; + +import de.intranda.api.annotation.IResource; +import de.intranda.api.annotation.wa.Dataset; +import de.intranda.api.annotation.wa.TextualResource; +import de.intranda.api.annotation.wa.TypedResource; +import io.goobi.viewer.api.rest.resourcebuilders.AnnotationsResourceBuilder; +import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.exceptions.DAOException; +import io.goobi.viewer.model.annotation.PersistentAnnotation; +import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign; +import io.goobi.viewer.model.crowdsourcing.questions.Question; +import io.goobi.viewer.model.security.user.User; + +public class ExcelRenderer { + + private static final Logger logger = LoggerFactory.getLogger(ExcelRenderer.class); + + private final AnnotationsResourceBuilder annotationBuilder; + + /** + * @param annotationBuilder + */ + public ExcelRenderer(AnnotationsResourceBuilder annotationBuilder) { + this.annotationBuilder = annotationBuilder; + } + + public HSSFWorkbook render(Map> annotationMap) { + if (annotationMap == null) { + throw new IllegalArgumentException("No annotations given"); + } + + if (annotationMap.size() == 0) { + throw new IllegalArgumentException("Empty annotations map"); + } + + HSSFWorkbook wb = new HSSFWorkbook(); + + for (String type : annotationMap.keySet()) { + HSSFSheet sheet = wb.createSheet(type); + sheet.setDefaultColumnWidth(30); + short height = 300; + sheet.setDefaultRowHeight(height); + createHeaderRow(sheet); + List annotations = annotationMap.get(type); + createDataRows(sheet, annotations); + } + + return wb; + } + + /** + * @param sheet + * @param annotations + */ + public void createDataRows(HSSFSheet sheet, List annotations) { + int rowCounter = 1; + for (PersistentAnnotation annotation : annotations) { + try { + createDataRow(annotation, sheet, rowCounter); + } catch (DAOException e) { + logger.error("Error creating data row for annotation " + annotation); + } + rowCounter++; + } + } + + /** + * @param annotation + * @param sheet + * @param rowCounter + * @throws DAOException + */ + public void createDataRow(PersistentAnnotation annotation, HSSFSheet sheet, int rowCounter) throws DAOException { + HSSFRow row = sheet.createRow(rowCounter); + row.setRowStyle(getDataCellStyle(sheet.getWorkbook())); + HSSFCell idCell = row.createCell(0, CellType.STRING); + idCell.setCellValue(annotation.getId().toString()); + HSSFCell recordCell = row.createCell(1, CellType.STRING); + recordCell.setCellValue(annotation.getTargetPI()); + HSSFCell pageCell = row.createCell(2, CellType.STRING); + String pageOrder = Optional.ofNullable(annotation.getTargetPageOrder()).map(i -> i.toString()).orElse(""); + pageCell.setCellValue(pageOrder); + HSSFCell campaignCell = row.createCell(3, CellType.STRING); + campaignCell.setCellValue(Optional.ofNullable(annotation.getGenerator()).map(Question::getOwner).map(Campaign::getTitle).orElse("")); + HSSFCell authorCell = row.createCell(4, CellType.STRING); + authorCell.setCellValue(Optional.ofNullable(annotation.getCreator()).map(User::getDisplayName).orElse("")); + HSSFCell bodyCell = row.createCell(5, CellType.STRING); + bodyCell.setCellValue(getBodyValues(annotation).get(0)); + + setCellStyles(row, getDataCellStyle(sheet.getWorkbook())); + + } + + + + /** + * @param sheet + */ + public void createHeaderRow(HSSFSheet sheet) { + HSSFRow titleRow = sheet.createRow(0); + titleRow.setRowStyle(getHeaderCellStyle(sheet.getWorkbook())); + HSSFCell idCell = titleRow.createCell(0, CellType.STRING); + idCell.setCellStyle(getHeaderCellStyle(sheet.getWorkbook())); + idCell.setCellValue("ID"); + HSSFCell recordCell = titleRow.createCell(1, CellType.STRING); + recordCell.setCellValue("in"); + HSSFCell pageCell = titleRow.createCell(2, CellType.STRING); + pageCell.setCellValue("on page"); + HSSFCell campaignCell = titleRow.createCell(3, CellType.STRING); + campaignCell.setCellValue("Campaign"); + HSSFCell authorCell = titleRow.createCell(4, CellType.STRING); + authorCell.setCellValue("Author"); + HSSFCell bodyCell = titleRow.createCell(5, CellType.STRING); + bodyCell.setCellValue("values"); + + setCellStyles(titleRow, getHeaderCellStyle(sheet.getWorkbook())); + } + + private List getBodyValues(PersistentAnnotation anno) { + try { + IResource bodyResource = annotationBuilder.getBodyAsResource(anno); + String type = "unknown"; + if(bodyResource instanceof TypedResource) { + type = ((TypedResource) bodyResource).getType(); + } + switch(type) { + case "TextualBody": + TextualResource res = (TextualResource)bodyResource; + return Collections.singletonList(res.getText()); + case "AuthorityResource": + return Collections.singletonList(bodyResource.getId().toString()); + case "Dataset": + Dataset dataset = (Dataset)bodyResource; + StringBuilder sb = new StringBuilder(); + for (Entry> entry : dataset.getData().entrySet()) { + String label = entry.getKey(); + String value = entry.getValue().stream().collect(Collectors.joining(", ")); + sb.append(label).append(": ").append(value).append("\t"); + } + return Collections.singletonList(sb.toString().trim()); + default: + return Collections.singletonList(anno.getBody()); + + } + } catch(IOException e) { + logger.error("Error writing body fields", e); + return Collections.singletonList(anno.getBody()); + } + } + + /** + * + */ + private void setCellStyles(Row row, CellStyle style) { + Iterator cells = row.cellIterator(); + while(cells.hasNext()) { + Cell cell = cells.next(); + cell.setCellStyle(style); + } + } + + private CellStyle getHeaderCellStyle(HSSFWorkbook wb) { + HSSFCellStyle style = wb.createCellStyle(); + style.setAlignment(HorizontalAlignment.LEFT); + HSSFFont font = wb.createFont(); + font.setBold(true); + style.setFont(font); + return style; + } + + /** + * @param workbook + * @return + */ + private HSSFCellStyle getDataCellStyle(HSSFWorkbook wb) { + HSSFCellStyle style = wb.createCellStyle(); +// style.setAlignment(HorizontalAlignment.RIGHT); + HSSFFont font = wb.createFont(); + style.setFont(font); + return style; + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveEntry.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveEntry.java new file mode 100644 index 00000000000..0ace07eefbb --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveEntry.java @@ -0,0 +1,737 @@ +package io.goobi.viewer.model.archives; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import org.apache.solr.common.SolrDocument; +import org.apache.solr.common.SolrException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.controller.SolrConstants; +import io.goobi.viewer.exceptions.IndexUnreachableException; +import io.goobi.viewer.exceptions.PresentationException; + +public class ArchiveEntry { + + private static final Logger logger = LoggerFactory.getLogger(ArchiveEntry.class); + + // parent node + private ArchiveEntry parentNode; + // list contains all child elements + private List subEntryList = new ArrayList<>(); + // order number of the current element within the current hierarchy + private Integer orderNumber; + // hierarchy level + private Integer hierarchyLevel; + // c@id + private String id; + // display label + private String label; + // node is open/closed + private boolean displayChildren; + // node is search hit + private boolean searchHit; + // node type - @level + private String nodeType; + // display node in a search result + private boolean displaySearch; + // true if the validation of all metadata fields was successful + private boolean valid = true; + + private String descriptionLevel; + + private boolean visible = true; + + private boolean expanded = false; + + private String associatedRecordPi; + + /* 1. metadata for Identity Statement Area */ + // Reference code(s) + // Title + // private String unittitle; // did/unittitle + // Date(s) + // Level of description + // Extent and medium of the unit of description (quantity, bulk, or size) + private List identityStatementAreaList = new ArrayList<>(); + + /* 2. Context Area */ + // Name of creator(s) + // Administrative | Biographical history + // Archival history + // Immediate source of acquisition or transfer + private List contextAreaList = new ArrayList<>(); + + /* 3. Content and Structure Area */ + // Scope and content + // Appraisal, destruction and scheduling information + // Accruals + // System of arrangement + private List contentAndStructureAreaAreaList = new ArrayList<>(); + + /* 4. Condition of Access and Use Area */ + // Conditions governing access + // Conditions governing reproduction + // Language | Scripts of material + // Physical characteristics and technical requirements + // Finding aids + private List accessAndUseAreaList = new ArrayList<>(); + + /* 5. Allied Materials Area */ + // Existence and location of originals + // Existence and location of copies + // Related units of description + // Publication note + private List alliedMaterialsAreaList = new ArrayList<>(); + + /* 6. Note Area */ + // Note + private List notesAreaList = new ArrayList<>(); + + /* 7. Description Control Area */ + // Archivist's Note + // Rules or Conventions + // Date(s) of descriptions + private List descriptionControlAreaList = new ArrayList<>(); + + public ArchiveEntry(Integer order, Integer hierarchy) { + this.orderNumber = order; + this.hierarchyLevel = hierarchy; + } + + public void addSubEntry(ArchiveEntry other) { + subEntryList.add(other); + other.setParentNode(this); + } + + public void removeSubEntry(ArchiveEntry other) { + subEntryList.remove(other); + reOrderElements(); + } + + public void reOrderElements() { + int order = 0; + for (ArchiveEntry entry : subEntryList) { + entry.setOrderNumber(order++); + } + } + + /** + * + * @param ignoreDisplayChildren + * @return + */ + public List getAsFlatList(boolean ignoreDisplayChildren) { + List list = new LinkedList<>(); + list.add(this); + if (displayChildren || ignoreDisplayChildren) { + if (subEntryList != null && !subEntryList.isEmpty()) { + for (ArchiveEntry ds : subEntryList) { + list.addAll(ds.getAsFlatList(ignoreDisplayChildren)); + // logger.trace("ID: {}, level {}", ds.getId(), ds.getHierarchy()); + } + } + } + return list; + } + + public void findAssociatedRecordPi() throws PresentationException { + if (id == null) { + associatedRecordPi = ""; + return; + } + try { + SolrDocument doc = DataManager.getInstance() + .getSearchIndex() + .getFirstDoc("+" + SolrConstants.ARCHIVE_ENTRY_ID + ":" + id, Collections.singletonList(SolrConstants.PI)); + if (doc != null) { + associatedRecordPi = (String) doc.getFieldValue(SolrConstants.PI); + } + if (associatedRecordPi == null) { + associatedRecordPi = ""; + } + } catch (SolrException | IndexUnreachableException e) { + logger.error("Error reading solr database:" + e); + } + } + + public boolean isHasChildren() { + return !subEntryList.isEmpty(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + ArchiveEntry other = (ArchiveEntry) obj; + if (hierarchyLevel == null) { + if (other.hierarchyLevel != null) { + return false; + } + } else if (!hierarchyLevel.equals(other.hierarchyLevel)) { + return false; + } + if (orderNumber == null) { + if (other.orderNumber != null) { + return false; + } + } else if (!orderNumber.equals(other.orderNumber)) { + return false; + } + if (parentNode == null && other.parentNode == null) { + return true; + } + if (parentNode == null && other.parentNode != null) { + return false; + } + if (parentNode != null && other.parentNode == null) { + return false; + } + + if (!parentNode.getOrderNumber().equals(other.parentNode.getOrderNumber())) { + return false; + } + if (!parentNode.getHierarchyLevel().equals(other.parentNode.getHierarchyLevel())) { + return false; + } + + return true; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((hierarchyLevel == null) ? 0 : hierarchyLevel.hashCode()); + result = prime * result + ((orderNumber == null) ? 0 : orderNumber.hashCode()); + result = prime * result + ((parentNode == null) ? 0 : parentNode.getHierarchyLevel().hashCode()); + result = prime * result + ((parentNode == null) ? 0 : parentNode.getOrderNumber().hashCode()); + return result; + } + + public void updateHierarchy() { + // root node + if (parentNode == null) { + hierarchyLevel = 0; + } else { + hierarchyLevel = parentNode.getHierarchyLevel() + 1; + } + + for (ArchiveEntry child : subEntryList) { + child.updateHierarchy(); + } + } + + public void markAsFound(boolean keepChildrenVisible) { + displaySearch = true; + searchHit = true; + + if (parentNode != null) { + ArchiveEntry node = parentNode; + while (!node.isDisplaySearch()) { + node.setDisplaySearch(true); + if (node.parentNode != null) { + node = node.parentNode; + } + } + } + if (keepChildrenVisible) { + for (ArchiveEntry child : this.subEntryList) { + child.setDisplaySearch(true, true); + } + } + } + + public void resetFoundList() { + // logger.trace("resetFoundList: {}", id); + displaySearch = false; + searchHit = false; + if (subEntryList != null) { + for (ArchiveEntry ds : subEntryList) { + ds.resetFoundList(); + } + } + } + + public List getSearchList() { + List list = new LinkedList<>(); + if (displaySearch) { + list.add(this); + if (subEntryList != null) { + for (ArchiveEntry child : subEntryList) { + list.addAll(child.getSearchList()); + } + } + } + return list; + } + + /** + * + * @param offset + */ + public void shiftHierarchy(int offset) { + this.hierarchyLevel += offset; + if (isHasChildren()) { + for (ArchiveEntry sub : subEntryList) { + sub.shiftHierarchy(offset); + } + } + } + + /** + * Expands and sets visible all ancestors of this node and expands siblings of this node. + */ + public void expandUp() { + if (parentNode == null) { + return; + } + + parentNode.setVisible(visible); + parentNode.expand(); + parentNode.expandUp(); + } + + /** + * Expands this entry and sets all sub-entries visible if their immediate parent is expanded. + */ + public void expand() { + // logger.trace("expand: {}", id); + if (!isHasChildren()) { + return; + } + + setExpanded(true); + setChildrenVisibility(true); + } + + /** + * Collapses this entry and hides all sub-entries. + */ + public void collapse() { + // logger.trace("collapse: {}", id); + if (!isHasChildren()) { + return; + } + + setExpanded(false); + setChildrenVisibility(false); + } + + /** + * + * @param visible + */ + void setChildrenVisibility(boolean visible) { + if (!isHasChildren()) { + return; + } + + for (ArchiveEntry sub : subEntryList) { + sub.setVisible(visible); + if (sub.isExpanded() && sub.isHasChildren()) { + sub.setChildrenVisibility(visible); + } + } + } + + /** + * @return the parentNode + */ + public ArchiveEntry getParentNode() { + return parentNode; + } + + /** + * @param parentNode the parentNode to set + */ + public void setParentNode(ArchiveEntry parentNode) { + this.parentNode = parentNode; + } + + /** + * @return the subEntryList + */ + public List getSubEntryList() { + return subEntryList; + } + + /** + * @param subEntryList the subEntryList to set + */ + public void setSubEntryList(List subEntryList) { + this.subEntryList = subEntryList; + } + + /** + * @return the orderNumber + */ + public Integer getOrderNumber() { + return orderNumber; + } + + /** + * @param orderNumber the orderNumber to set + */ + public void setOrderNumber(Integer orderNumber) { + this.orderNumber = orderNumber; + } + + /** + * @return the hierarchyLevel + */ + public Integer getHierarchyLevel() { + return hierarchyLevel; + } + + /** + * @param hierarchyLevel the hierarchyLevel to set + */ + public void setHierarchyLevel(Integer hierarchyLevel) { + this.hierarchyLevel = hierarchyLevel; + } + + /** + * @return the id + */ + public String getId() { + return id; + } + + /** + * @param id the id to set + */ + public void setId(String id) { + this.id = id; + } + + /** + * @return the label + */ + public String getLabel() { + return label; + } + + /** + * @param label the label to set + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @return the displayChildren + */ + public boolean isDisplayChildren() { + return displayChildren; + } + + /** + * @param displayChildren the displayChildren to set + */ + public void setDisplayChildren(boolean displayChildren) { + this.displayChildren = displayChildren; + } + + /** + * @return the searchHit + */ + public boolean isSearchHit() { + return searchHit; + } + + /** + * @param searchHit the searchHit to set + */ + public void setSearchHit(boolean searchHit) { + this.searchHit = searchHit; + } + + /** + * @return the nodeType + */ + public String getNodeType() { + return nodeType; + } + + /** + * @param nodeType the nodeType to set + */ + public void setNodeType(String nodeType) { + this.nodeType = nodeType; + } + + /** + * @return the displaySearch + */ + public boolean isDisplaySearch() { + return displaySearch; + } + + /** + * @param displaySearch the displaySearch to set + */ + public void setDisplaySearch(boolean displaySearch) { + this.setDisplaySearch(displaySearch, false); + } + + public void setDisplaySearch(boolean displaySearch, boolean recursive) { + this.displaySearch = displaySearch; + if (recursive) { + for (ArchiveEntry child : this.subEntryList) { + child.setDisplaySearch(displaySearch, recursive); + } + } + } + + public ArchiveMetadataField getIdentityStatementAreaField(String name) { + for (ArchiveMetadataField field : identityStatementAreaList) { + if (field.getLabel().equals(name)) { + return field; + } + } + + return null; + } + + public List getAllAreaLists() { + logger.trace("getAllAreaLists ({})", id); + List ret = new ArrayList<>(getIdentityStatementAreaList().size() + + getContextAreaList().size() + + getContentAndStructureAreaAreaList().size() + + getAccessAndUseAreaList().size() + + getAlliedMaterialsAreaList().size() + + getNotesAreaList().size() + + getDescriptionControlAreaList().size()); + ret.addAll(getIdentityStatementAreaList()); + ret.addAll(getContextAreaList()); + ret.addAll(getContentAndStructureAreaAreaList()); + ret.addAll(getAccessAndUseAreaList()); + ret.addAll(getAlliedMaterialsAreaList()); + ret.addAll(getNotesAreaList()); + ret.addAll(getDescriptionControlAreaList()); + + logger.trace("getAllAreaLists END"); + return ret; + } + + /** + * @return the identityStatementAreaList + */ + public List getIdentityStatementAreaList() { + // logger.trace("getIdentityStatementAreaList ({})", id); + return identityStatementAreaList; + } + + /** + * @param identityStatementAreaList the identityStatementAreaList to set + */ + public void setIdentityStatementAreaList(List identityStatementAreaList) { + this.identityStatementAreaList = identityStatementAreaList; + } + + /** + * @return the contextAreaList + */ + public List getContextAreaList() { + // logger.trace("getContextAreaList ({})", id); + return contextAreaList; + } + + /** + * @param contextAreaList the contextAreaList to set + */ + public void setContextAreaList(List contextAreaList) { + this.contextAreaList = contextAreaList; + } + + /** + * @return the contentAndStructureAreaAreaList + */ + public List getContentAndStructureAreaAreaList() { + // logger.trace("getContentAndStructureAreaAreaList ({})", id); + return contentAndStructureAreaAreaList; + } + + /** + * @param contentAndStructureAreaAreaList the contentAndStructureAreaAreaList to set + */ + public void setContentAndStructureAreaAreaList(List contentAndStructureAreaAreaList) { + this.contentAndStructureAreaAreaList = contentAndStructureAreaAreaList; + } + + /** + * @return the accessAndUseAreaList + */ + public List getAccessAndUseAreaList() { + // logger.trace("getAccessAndUseAreaList ({})", id); + return accessAndUseAreaList; + } + + /** + * @param accessAndUseAreaList the accessAndUseAreaList to set + */ + public void setAccessAndUseAreaList(List accessAndUseAreaList) { + this.accessAndUseAreaList = accessAndUseAreaList; + } + + /** + * @return the alliedMaterialsAreaList + */ + public List getAlliedMaterialsAreaList() { + // logger.trace("getAlliedMaterialsAreaList ({})", id); + return alliedMaterialsAreaList; + } + + /** + * @param alliedMaterialsAreaList the alliedMaterialsAreaList to set + */ + public void setAlliedMaterialsAreaList(List alliedMaterialsAreaList) { + this.alliedMaterialsAreaList = alliedMaterialsAreaList; + } + + /** + * @return the notesAreaList + */ + public List getNotesAreaList() { + // logger.trace("getNotesAreaList ({})", id); + return notesAreaList; + } + + /** + * @param notesAreaList the notesAreaList to set + */ + public void setNotesAreaList(List notesAreaList) { + this.notesAreaList = notesAreaList; + } + + /** + * @return the descriptionControlAreaList + */ + public List getDescriptionControlAreaList() { + // logger.trace("getDescriptionControlAreaList ({})", id); + return descriptionControlAreaList; + } + + /** + * @param descriptionControlAreaList the descriptionControlAreaList to set + */ + public void setDescriptionControlAreaList(List descriptionControlAreaList) { + this.descriptionControlAreaList = descriptionControlAreaList; + } + + /** + * @return the valid + */ + public boolean isValid() { + return valid; + } + + /** + * @param valid the valid to set + */ + public void setValid(boolean valid) { + this.valid = valid; + } + + /** + * @return the descriptionLevel + */ + public String getDescriptionLevel() { + return descriptionLevel; + } + + /** + * @param descriptionLevel the descriptionLevel to set + */ + public void setDescriptionLevel(String descriptionLevel) { + this.descriptionLevel = descriptionLevel; + } + + + /** + * @return the visible + */ + public boolean isVisible() { + return visible; + } + + /** + * @param visible the visible to set + */ + public void setVisible(boolean visible) { + this.visible = visible; + } + + /** + * @return the expanded + */ + public boolean isExpanded() { + return expanded; + } + + /** + * @param expanded the expanded to set + */ + public void setExpanded(boolean expanded) { + this.expanded = expanded; + } + + /** + * @return the hasChild + */ + public boolean isHasChild() { + return !subEntryList.isEmpty(); + } + + /** + * @return the associatedRecordPi + * @throws IndexUnreachableException + * @throws PresentationException + */ + public String getAssociatedRecordPi() throws PresentationException, IndexUnreachableException { + if (associatedRecordPi == null) { + findAssociatedRecordPi(); + } + + return associatedRecordPi; + } + + /** + * Get the parent node hierarchy of this node, optionally including the node itself The list is sorted with hightest hierarchy level first, so the + * node itself will always be the last element, if included + * + * @param includeSelf + * @return + */ + public List getAncestors(boolean includeSelf) { + List ancestors = new ArrayList<>(); + if (includeSelf) { + ancestors.add(this); + } + ArchiveEntry parent = this.parentNode; + while (parent != null) { + ancestors.add(parent); + parent = parent.parentNode; + } + Collections.reverse(ancestors); + return ancestors; + + } + + @Override + public String toString() { + return id; + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveMetadataField.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveMetadataField.java new file mode 100644 index 00000000000..d807ddf2c4c --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveMetadataField.java @@ -0,0 +1,167 @@ +package io.goobi.viewer.model.archives; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.lang.StringUtils; + +public class ArchiveMetadataField { + + /** contains the internal name of the field. The value can be used to translate the field in the messages files */ + private String label; + + /** + * metadata level, allowed values are 1-7: + *

    + *
  • 1: metadata for Identity Statement Area
  • + *
  • 2: Context Area
  • + *
  • 3: Content and Structure Area
  • + *
  • 4: Condition of Access and Use Area
  • + *
  • 5: Allied Materials Area
  • + *
  • 6: Note Area
  • + *
  • 7: Description Control Area
  • + *
+ */ + private Integer type; + + /** contains a relative path to the ead value. The root of the xpath is either the {@code} element or the {@code} element */ + private String xpath; + + /** type of the xpath return value, can be text, attribute, element (default) */ + private String xpathType; + + /** contains the metadata values */ + private List values; + + /** links to the ead node. Required to set the title field for the entry while parsing metadata */ + // @ToString.Exclude + private ArchiveEntry eadEntry; + + + + + public ArchiveMetadataField(String label, Integer type, String xpath, String xpathType) { + this.label = label; + this.type = type; + this.xpath = xpath; + this.xpathType = xpathType; + } + + public boolean isFilled() { + if (values == null || values.isEmpty()) { + return false; + } + for (FieldValue val : values) { + if (StringUtils.isNotBlank(val.getValue())) { + return true; + } + } + return false; + } + + public void addFieldValue(FieldValue value) { + if (values == null) { + values = new ArrayList<>(); + } + values.add(value); + } + + public void addValue() { + if (values == null) { + values = new ArrayList<>(); + } + values.add(new FieldValue(this)); + } + + /** + * @return the name + */ + public String getLabel() { + return label; + } + + /** + * @param name the name to set + */ + public void setLabel(String label) { + this.label = label; + } + + /** + * @return the level + */ + public Integer getType() { + return type; + } + + /** + * @param level the level to set + */ + public void setType(Integer type) { + this.type = type; + } + + /** + * @return the xpathType + */ + public String getXpathType() { + return xpathType; + } + + /** + * @param xpathType the xpathType to set + */ + public void setXpathType(String xpathType) { + this.xpathType = xpathType; + } + + public String getValue() { + if (values == null || values.isEmpty()) { + return null; + } + + return values.get(0).getValue(); + } + + /** + * @return the values + */ + public List getValues() { + return values; + } + + /** + * @param values the values to set + */ + public void setValues(List values) { + this.values = values; + } + + /** + * @return the xpath + */ + public String getXpath() { + return xpath; + } + + /** + * @param xpath the xpath to set + */ + public void setXpath(String xpath) { + this.xpath = xpath; + } + + /** + * @return the eadEntry + */ + public ArchiveEntry getEadEntry() { + return eadEntry; + } + + /** + * @param eadEntry the eadEntry to set + */ + public void setEadEntry(ArchiveEntry eadEntry) { + this.eadEntry = eadEntry; + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveResource.java new file mode 100644 index 00000000000..738ea30eda5 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveResource.java @@ -0,0 +1,50 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.model.archives; + +import java.time.Instant; + +/** + * @author florian + * + */ +public class ArchiveResource { + + public final String databaseName; + public final String resourceName; + public final Instant lastModified; + + /** + * + */ + public ArchiveResource(String database, String resource, String modifiedDate) { + this.databaseName = database; + this.resourceName = resource; + this.lastModified = Instant.parse(modifiedDate); + } + + public String getCombinedName() { + return databaseName + " - " + resourceName; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return getCombinedName(); + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveTree.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveTree.java new file mode 100644 index 00000000000..48a043615d2 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/ArchiveTree.java @@ -0,0 +1,432 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.model.archives; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Table of contents and associated functionality for a record. + */ +public class ArchiveTree implements Serializable { + + private static final long serialVersionUID = 1798213211987072214L; + + private static final Logger logger = LoggerFactory.getLogger(ArchiveTree.class); + + /** Constant DEFAULT_GROUP="_DEFAULT" */ + public static final String DEFAULT_GROUP = "_DEFAULT"; + + public static int defaultCollapseLevel = 1; + + /** Actual root of the document (even if it's not part of the displayed tree) */ + private ArchiveEntry trueRootElement = null; + + /** Currently displayed tree. Can be partial after a search, etc. */ + private List flatEntryList; + + /** TOC element map. */ + private Map> entryMap = new HashMap<>(1); + + /** Actively selected entry */ + private ArchiveEntry selectedEntry; + + private boolean treeBuilt = false; + + /** + *

+ * Constructor for TOC. + *

+ */ + public ArchiveTree() { + logger.trace("new EADTree()"); + } + + public void generate(ArchiveEntry root) { + if (root == null) { + throw new IllegalArgumentException("root may not be null"); + } + + setTrueRootElement(root); + + // If root has just one child, use it as new root + if (root.getSubEntryList().size() == 1) { + root = root.getSubEntryList().get(0); + root.shiftHierarchy(-1); + } + + List tree = root.getAsFlatList(true); + entryMap.put(DEFAULT_GROUP, tree); + } + + /** + *

+ * getViewForGroup. + *

+ * + * @param group a {@link java.lang.String} object. + * @return a {@link java.util.List} object. + */ + public List getViewForGroup(String group) { + if (entryMap != null) { + return entryMap.get(group); + } + + return null; + } + + /** + *

+ * getTreeViewForGroup. + *

+ * + * @param group a {@link java.lang.String} object. + * @should call buildTree and set maxTocDepth correctly + * @return a {@link java.util.List} object. + */ + public List getTreeViewForGroup(String group) { + if (!treeBuilt) { + buildTree(group, defaultCollapseLevel); + } + return getViewForGroup(group); + } + + /** + *

+ * getFlatView. + *

+ * + * @return a {@link java.util.List} object. + */ + public List getFlatView() { + // logger.trace("getFlatView"); + return getViewForGroup(DEFAULT_GROUP); + } + + /** + *

+ * getTreeView. + *

+ * + * @return a {@link java.util.List} object. + */ + public List getTreeView() { + return getTreeViewForGroup(DEFAULT_GROUP); + } + + /** + * + * @param group + * @param collapseLevel + */ + private void buildTree(String group, int collapseLevel) { + logger.trace("buildTree"); + if (group == null) { + throw new IllegalArgumentException("group may not be null"); + } + + synchronized (this) { + if (entryMap == null) { + return; + } + int lastLevel = 0; + int lastParent = 0; + for (ArchiveEntry entry : entryMap.get(group)) { + // Current element index + int index = entryMap.get(group).indexOf(entry); + if (lastLevel < entry.getHierarchyLevel() && index > 0) { + if (entry.getHierarchyLevel() > collapseLevel) { + entryMap.get(group).get(index - 1).setExpanded(false); + entry.setVisible(false); + } else { + entryMap.get(group).get(index - 1).setExpanded(true); + } + + for (int i = index + 1; i < entryMap.get(group).size(); i++) { + ArchiveEntry tc = entryMap.get(group).get(i); + if (tc.getHierarchyLevel() > collapseLevel) { + tc.setVisible(false); + } + } + + } + lastParent = index; + lastLevel = entry.getHierarchyLevel(); + } + treeBuilt = true; + resetCollapseLevel(getRootElement(), collapseLevel); + } + } + + /** + * + * @param entry + * @param maxDepth + */ + public void resetCollapseLevel(ArchiveEntry entry, int maxDepth) { + if (entry == null) { + return; + } + + if (entry.getHierarchyLevel() <= maxDepth) { + entry.setVisible(true); + entry.setExpanded(entry.getHierarchyLevel() != maxDepth); + } else { + entry.setVisible(false); + entry.setExpanded(false); + } + + if (entry.getSubEntryList() != null && !entry.getSubEntryList().isEmpty()) { + for (ArchiveEntry child : entry.getSubEntryList()) { + resetCollapseLevel(child, maxDepth); + } + } + } + + /** + * @return the selectedEntry + */ + public ArchiveEntry getSelectedEntry() { + return selectedEntry; + } + + /** + * @param selectedEntry the selectedEntry to set + */ + public void setSelectedEntry(ArchiveEntry selectedEntry) { + logger.trace("setSelectedEntry: {}", selectedEntry != null ? selectedEntry.getId() : null); + this.selectedEntry = selectedEntry; + } + + /** + * @return the trueRootElement + */ + public ArchiveEntry getTrueRootElement() { + return trueRootElement; + } + + /** + * @param trueRootElement the trueRootElement to set + */ + public void setTrueRootElement(ArchiveEntry trueRootElement) { + this.trueRootElement = trueRootElement; + } + + /** + * + * @return + */ + public ArchiveEntry getRootElement() { + return getRootElement(DEFAULT_GROUP); + } + + /** + * + * @param group + * @return + */ + public ArchiveEntry getRootElement(String group) { + if (group == null || entryMap == null || entryMap.isEmpty()) { + return null; + } + + return entryMap.get(group).get(0); + } + + /** + *

+ * expandAll. + *

+ */ + public void expandAll() { + logger.trace("expandAll"); + if (entryMap == null) { + return; + } + + for (ArchiveEntry tcElem : entryMap.get(DEFAULT_GROUP)) { + tcElem.setVisible(true); + if (tcElem.isHasChild()) { + tcElem.setExpanded(true); + } + } + } + + /** + *

+ * collapseAll. + *

+ */ + public void collapseAll() { + if (entryMap == null) { + return; + } + + collapseAll(false); + } + + /** + * + * @param collapseAllEntries If true, all invisible child children will also be collapsed + */ + public void collapseAll(boolean collapseAllEntries) { + logger.trace("collapseAll"); + if (entryMap == null) { + return; + } + + for (ArchiveEntry tcElem : entryMap.get(DEFAULT_GROUP)) { + if (tcElem.getHierarchyLevel() == 0) { + tcElem.setExpanded(false); + } else { + if (collapseAllEntries) { + tcElem.setExpanded(false); + } + tcElem.setVisible(false); + } + } + } + + /** + * @return the entryMap + */ + Map> getEntryMap() { + return entryMap; + } + + /** + *

+ * getTocElements. + *

+ * + * @return a {@link java.util.List} object. + */ + public List getTocElements() { + if (entryMap != null) { + return entryMap.get(DEFAULT_GROUP); + } + + return null; + } + + /** + * Get the hierarchical tree as a flat list + * + * @return + */ + + public List getFlatEntryList() { + if (flatEntryList == null) { + if (trueRootElement != null) { + flatEntryList = new LinkedList<>(); + flatEntryList.addAll(trueRootElement.getAsFlatList(false)); + } + } + return flatEntryList; + } + + /** + * + * @return the {@link ArchiveEntry} with the given identifier if it exists in the tree; null otherwise + * @param identifier + */ + public ArchiveEntry getEntryById(String identifier) { + return findEntry(identifier, getRootElement()).orElse(null); + } + + /** + * + * @param searchValue + */ + public void search(String searchValue) { + if (getRootElement() == null) { + logger.error("Database not loaded"); + return; + } + + if (StringUtils.isNotBlank(searchValue)) { + // hide all elements + getRootElement().resetFoundList(); + // search in all/some metadata fields of all elements? + + // for now: search only labels + searchInNode(getRootElement(), searchValue); + + // fill flatList with displayable fields + flatEntryList = getRootElement().getSearchList(); + } else { + resetSearch(); + } + } + + /** + * + * @param node + * @param searchValue + */ + static void searchInNode(ArchiveEntry node, String searchValue) { + if (node.getId() != null && node.getId().equals(searchValue)) { + // ID match + node.markAsFound(true); + } else if (node.getLabel() != null && node.getLabel().toLowerCase().contains(searchValue.toLowerCase())) { + // mark element + all parents as displayable + node.markAsFound(true); + } + if (node.getSubEntryList() != null) { + for (ArchiveEntry child : node.getSubEntryList()) { + searchInNode(child, searchValue); + } + } + } + + /** + * Return this node if it has the given identifier or the first of its descendents with the identifier + * + * @param identifier + * @param topNode + * @return + */ + private Optional findEntry(String identifier, ArchiveEntry node) { + if (StringUtils.isNotBlank(identifier)) { + if (identifier.equals(node.getId())) { + return Optional.of(node); + } + if (node.getSubEntryList() != null) { + for (ArchiveEntry child : node.getSubEntryList()) { + Optional find = findEntry(identifier, child); + if (find.isPresent()) { + return find; + } + } + } + } + + return Optional.empty(); + } + + public void resetSearch() { + trueRootElement.resetFoundList(); + flatEntryList = null; + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/BasexEADParser.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/BasexEADParser.java new file mode 100644 index 00000000000..e91215fafe0 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/BasexEADParser.java @@ -0,0 +1,468 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.model.archives; + +import java.io.IOException; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; +import org.apache.commons.lang.StringUtils; +import org.apache.http.client.ClientProtocolException; +import org.jdom2.Attribute; +import org.jdom2.Document; +import org.jdom2.Element; +import org.jdom2.JDOMException; +import org.jdom2.Namespace; +import org.jdom2.Text; +import org.jdom2.filter.Filters; +import org.jdom2.input.SAXBuilder; +import org.jdom2.input.sax.XMLReaders; +import org.jdom2.xpath.XPathExpression; +import org.jdom2.xpath.XPathFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.goobi.viewer.controller.NetTools; +import io.goobi.viewer.exceptions.HTTPException; + +/** + * Loads and parses EAD documents from BaseX databases. + */ +public class BasexEADParser { + + private static final Logger logger = LoggerFactory.getLogger(BasexEADParser.class); + + public static final Namespace NAMESPACE_EAD = Namespace.getNamespace("ead", "urn:isbn:1-931666-22-9"); + + private static final XPathFactory xFactory = XPathFactory.instance(); + + private final String basexUrl; + + private String selectedDatabase; + + private List configuredFields; + + // private List eventList; + // private List editorList; + + /** + * + * @param configFilePath + * @throws ConfigurationException + */ + public BasexEADParser(String basexUrl) { + this.basexUrl = basexUrl; + } + + /** + * Get the database names and file names from the basex databases + * + * @return + * @throws HTTPException + * @throws IOException + * @throws ClientProtocolException + */ + public List getPossibleDatabases() throws ClientProtocolException, IOException, HTTPException { + String response = NetTools.getWebContentGET(basexUrl + "databases"); + if (StringUtils.isBlank(response)) { + return Collections.emptyList(); + } + + Document document; + try { + document = openDocument(response); + + Element root = document.getRootElement(); + List databaseList = root.getChildren("database"); + List ret = new ArrayList<>(); + for (Element db : databaseList) { + String dbName = db.getChildText("name"); + + Element details = db.getChild("details"); + for (Element resource : details.getChildren()) { + String resourceName = resource.getText(); + String lastUpdated = resource.getAttributeValue("modified-date"); + ArchiveResource eadResource = new ArchiveResource(dbName, resourceName, lastUpdated); + ret.add(eadResource); + } + + } + + return ret; + } catch (JDOMException e) { + logger.error("Failed to parse response from " + (basexUrl + "databases"), e); + return Collections.emptyList(); + } + } + + /** + * + * @param database + * @return + * @throws IOException + * @throws IllegalStateException + * @throws HTTPException + * @throws JDOMException + */ + public Document retrieveDatabaseDocument(String database) throws IOException, IllegalStateException, HTTPException, JDOMException { + if (StringUtils.isNotBlank(database)) { + String[] parts = database.split(" - "); + String url = basexUrl + "db/" + parts[0] + "/" + parts[1]; + logger.trace("URL: {}", url); + String response; + response = NetTools.getWebContentGET(url); + + // get xml root element + Document document = openDocument(response); + return document; + } + throw new IllegalStateException("Must provide database name before loading database"); + } + + /** + * Loads the given database and parses the EAD document. + * + * @param database + * @param document + * @return Root element of the loaded tree + * @throws IllegalStateException + * @throws IOException + * @throws HTTPException + * @throws JDOMException + * @throws ConfigurationException + */ + public ArchiveEntry loadDatabase(String database, Document document) + throws IllegalStateException, IOException, HTTPException, JDOMException, ConfigurationException { + + if (document == null) { + document = retrieveDatabaseDocument(database); + } + + // parse ead file + return parseEadFile(document); + } + + public List getDistinctDatabaseNames() throws ClientProtocolException, IOException, HTTPException { + List answer = new ArrayList<>(); + List completeList = getPossibleDatabases(); + for (ArchiveResource resource : completeList) { + String dbName = resource.databaseName; + if (!answer.contains(dbName)) { + answer.add(dbName); + } + } + + return answer; + } + + /** + * Reads the hierarchy from the given EAD document. + * + * @param document + * @return Root element of the tree + * @should parse document correctly + */ + ArchiveEntry parseEadFile(Document document) { + if (document == null) { + throw new IllegalArgumentException("document may not be null"); + } + + Element collection = document.getRootElement(); + Element eadElement = collection.getChild("ead", NAMESPACE_EAD); + ArchiveEntry rootElement = parseElement(1, 0, eadElement, configuredFields); + rootElement.setDisplayChildren(true); + + // Element archdesc = eadElement.getChild("archdesc", NAMESPACE_EAD); + // if (archdesc != null) { + // Element processinfoElement = archdesc.getChild("processinfo", NAMESPACE_EAD); + // if (processinfoElement != null) { + // Element list = processinfoElement.getChild("list", NAMESPACE_EAD); + // List entries = list.getChildren("item", NAMESPACE_EAD); + // eventList = new ArrayList<>(entries.size()); + // for (Element item : entries) { + // editorList.add(item.getText()); + // } + // } + // } + // Element control = eadElement.getChild("control", NAMESPACE_EAD); + // if (control != null) { + // Element maintenancehistory = control.getChild("maintenancehistory", NAMESPACE_EAD); + // if (maintenancehistory != null) { + // List events = maintenancehistory.getChildren("maintenancehistory", NAMESPACE_EAD); + // eventList = new ArrayList<>(events.size()); + // for (Element event : events) { + // String type = event.getChildText("eventtype", NAMESPACE_EAD); + // String date = event.getChildText("eventdatetime", NAMESPACE_EAD); + // eventList.add(new StringPair(type, date)); + // } + // } + // } + + return rootElement; + } + + /** + * read the metadata for the current xml node. - create an {@link ArchiveEntry} - execute the configured xpaths on the current node - add the + * metadata to one of the 7 levels - check if the node has sub nodes - call the method recursively for all sub nodes + * + * @param order + * @param hierarchy + * @param element + * @param configuredFields + * @return + */ + private static ArchiveEntry parseElement(int order, int hierarchy, Element element, List configuredFields) { + if (element == null) { + throw new IllegalArgumentException("element may not be null"); + } + if (configuredFields == null) { + throw new IllegalArgumentException("configuredFields may not be null"); + } + + ArchiveEntry entry = new ArchiveEntry(order, hierarchy); + + for (ArchiveMetadataField emf : configuredFields) { + + List stringValues = new ArrayList<>(); + if ("text".equalsIgnoreCase(emf.getXpathType())) { + XPathExpression engine = xFactory.compile(emf.getXpath(), Filters.text(), null, NAMESPACE_EAD); + List values = engine.evaluate(element); + for (Text value : values) { + String stringValue = value.getValue(); + stringValues.add(stringValue); + } + } else if ("attribute".equalsIgnoreCase(emf.getXpathType())) { + XPathExpression engine = xFactory.compile(emf.getXpath(), Filters.attribute(), null, NAMESPACE_EAD); + List values = engine.evaluate(element); + + for (Attribute value : values) { + String stringValue = value.getValue(); + stringValues.add(stringValue); + } + } else { + XPathExpression engine = xFactory.compile(emf.getXpath(), Filters.element(), null, NAMESPACE_EAD); + List values = engine.evaluate(element); + for (Element value : values) { + String stringValue = value.getValue(); + stringValues.add(stringValue); + } + } + addFieldToEntry(entry, emf, stringValues); + } + + Element eadheader = element.getChild("eadheader", NAMESPACE_EAD); + + entry.setId(element.getAttributeValue("id")); + + if (eadheader != null) { + entry.setLabel( + eadheader.getChild("filedesc", NAMESPACE_EAD).getChild("titlestmt", NAMESPACE_EAD).getChildText("titleproper", NAMESPACE_EAD)); + } + + // nodeType + // get child elements + List clist = null; + Element archdesc = element.getChild("archdesc", NAMESPACE_EAD); + if (archdesc != null) { + String type = archdesc.getAttributeValue("localtype"); + entry.setNodeType(type); + Element dsc = archdesc.getChild("dsc", NAMESPACE_EAD); + if (dsc != null) { + clist = dsc.getChildren("c", NAMESPACE_EAD); + } + + } else { + String type = element.getAttributeValue("otherlevel"); + entry.setNodeType(type); + + } + + if (StringUtils.isBlank(entry.getNodeType())) { + entry.setNodeType("folder"); + } + + // Set description level value + entry.setDescriptionLevel(element.getAttributeValue("level")); + + if (clist == null) { + clist = element.getChildren("c", NAMESPACE_EAD); + } + if (clist != null) { + int subOrder = 0; + int subHierarchy = hierarchy + 1; + for (Element c : clist) { + ArchiveEntry child = parseElement(subOrder, subHierarchy, c, configuredFields); + entry.addSubEntry(child); + child.setParentNode(entry); + subOrder++; + } + } + + // generate new id, if id is null + if (entry.getId() == null) { + entry.setId(String.valueOf(UUID.randomUUID())); + } + + return entry; + } + + /** + * Add the metadata to the configured level + * + * @param entry + * @param emf + * @param stringValue + */ + + private static void addFieldToEntry(ArchiveEntry entry, ArchiveMetadataField emf, List stringValues) { + if (StringUtils.isBlank(entry.getLabel()) && emf.getXpath().contains("unittitle") && stringValues != null && !stringValues.isEmpty()) { + entry.setLabel(stringValues.get(0)); + } + ArchiveMetadataField toAdd = new ArchiveMetadataField(emf.getLabel(), emf.getType(), emf.getXpath(), emf.getXpathType()); + toAdd.setEadEntry(entry); + + if (stringValues != null && !stringValues.isEmpty()) { + + // split single value into multiple fields + for (String stringValue : stringValues) { + FieldValue fv = new FieldValue(toAdd); + fv.setValue(stringValue); + toAdd.addFieldValue(fv); + } + } else { + FieldValue fv = new FieldValue(toAdd); + toAdd.addFieldValue(fv); + } + + switch (toAdd.getType()) { + case 1: + entry.getIdentityStatementAreaList().add(toAdd); + break; + case 2: + entry.getContextAreaList().add(toAdd); + break; + case 3: + entry.getContentAndStructureAreaAreaList().add(toAdd); + break; + case 4: + entry.getAccessAndUseAreaList().add(toAdd); + break; + case 5: + entry.getAlliedMaterialsAreaList().add(toAdd); + break; + case 6: + entry.getNotesAreaList().add(toAdd); + break; + case 7: + entry.getDescriptionControlAreaList().add(toAdd); + break; + } + + } + + /** + * Parse the string response from the basex database into a xml document + * + * @param response + * @return + * @throws IOException + * @throws JDOMException + */ + private static Document openDocument(String response) throws JDOMException, IOException { + // read response + SAXBuilder builder = new SAXBuilder(XMLReaders.NONVALIDATING); + builder.setFeature("http://xml.org/sax/features/validation", false); + builder.setFeature("http://apache.org/xml/features/nonvalidating/load-dtd-grammar", false); + builder.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + Document document = builder.build(new StringReader(response), "utf-8"); + return document; + + } + + /** + * + * @param node + * @param searchValue + */ + static void searchInNode(ArchiveEntry node, String searchValue) { + if (node.getId() != null && node.getId().equals(searchValue)) { + // ID match + node.markAsFound(true); + } else if (node.getLabel() != null && node.getLabel().toLowerCase().contains(searchValue.toLowerCase())) { + // mark element + all parents as displayable + node.markAsFound(true); + } + if (node.getSubEntryList() != null) { + for (ArchiveEntry child : node.getSubEntryList()) { + searchInNode(child, searchValue); + } + } + } + + /** + * Loads fields from the given configuration node. + * + * @param metadataConfig + * @return + * @throws ConfigurationException + */ + public BasexEADParser readConfiguration(HierarchicalConfiguration metadataConfig) throws ConfigurationException { + if (metadataConfig == null) { + throw new ConfigurationException("No basexMetadata configurations found"); + } + + metadataConfig.setListDelimiter('&'); + metadataConfig.setExpressionEngine(new XPathExpressionEngine()); + + try { + List configurations = metadataConfig.configurationsAt("/metadata"); + if (configurations == null) { + throw new ConfigurationException("No basexMetadata configurations found"); + } + configuredFields = new ArrayList<>(configurations.size()); + for (HierarchicalConfiguration hc : configurations) { + ArchiveMetadataField field = new ArchiveMetadataField(hc.getString("[@label]"), hc.getInt("[@type]"), hc.getString("[@xpath]"), + hc.getString("[@xpathType]", "element")); + configuredFields.add(field); + } + } catch (Exception e) { + throw new ConfigurationException("Error reading basexMetadata configuration", e); + } + + return this; + } + + /** + * @return the selectedDatabase + */ + public String getSelectedDatabase() { + return selectedDatabase; + } + + /** + * @return the basexUrl + */ + public String getBasexUrl() { + return basexUrl; + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/FieldValue.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/FieldValue.java new file mode 100644 index 00000000000..1d644b9f65b --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/archives/FieldValue.java @@ -0,0 +1,27 @@ +package io.goobi.viewer.model.archives; + +public class FieldValue { + + private String value; + private ArchiveMetadataField field; + + public FieldValue(ArchiveMetadataField field) { + this.field = field; + } + + /** + * @return the value + */ + public String getValue() { + return value; + } + + public void setValue(String value) { + if (field.getXpath().contains("unittitle")) { + field.getEadEntry().setLabel(value); + } else { + this.value = value; + } + + } +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/bookmark/Bookmark.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/bookmark/Bookmark.java index 08da9ffb2ab..9a56a4ab7a0 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/bookmark/Bookmark.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/bookmark/Bookmark.java @@ -32,10 +32,9 @@ import javax.persistence.TemporalType; import javax.persistence.Transient; -import org.apache.commons.text.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; import org.apache.solr.common.SolrDocument; -import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,7 +55,6 @@ import io.goobi.viewer.model.metadata.MetadataElement; import io.goobi.viewer.model.search.BrowseElement; import io.goobi.viewer.model.search.SearchHit; -import io.goobi.viewer.model.viewer.PageType; import io.goobi.viewer.model.viewer.StructElement; /** @@ -74,7 +72,8 @@ public class Bookmark implements Serializable { private static final Logger logger = LoggerFactory.getLogger(Bookmark.class); private static final String[] FIELDS = - { SolrConstants.THUMBNAIL, SolrConstants.DATAREPOSITORY, SolrConstants.MIMETYPE, SolrConstants.IDDOC, SolrConstants.PI, SolrConstants.ISWORK, SolrConstants.ISANCHOR }; + { SolrConstants.THUMBNAIL, SolrConstants.DATAREPOSITORY, SolrConstants.MIMETYPE, SolrConstants.IDDOC, SolrConstants.PI, + SolrConstants.ISWORK, SolrConstants.ISANCHOR }; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -114,13 +113,13 @@ public class Bookmark implements Serializable { @Transient private String url; - - @Transient + + @Transient private BrowseElement browseElement = null; - @Transient - private Boolean hasImages = null; - + @Transient + private Boolean hasImages = null; + /** * Empty constructor. */ @@ -318,9 +317,9 @@ public String getRepresentativeImageUrl(int width, int height) ThumbnailHandler thumbs = BeanUtils.getImageDeliveryBean().getThumbs(); if (order != null) { return thumbs.getThumbnailUrl(order, pi, width, height); - } else { - return thumbs.getThumbnailUrl(pi, width, height); } + + return thumbs.getThumbnailUrl(pi, width, height); } /** @@ -589,18 +588,18 @@ public String getMainTitle() { public void setMainTitle(String mainTitle) { this.mainTitle = mainTitle; } - + @JsonIgnore public MetadataElement getMetadataElement() throws IndexUnreachableException { SolrDocument doc = retrieveSolrDocument(); - Long iddoc = Long.parseLong((String)doc.getFirstValue(SolrConstants.IDDOC)); + Long iddoc = Long.parseLong((String) doc.getFirstValue(SolrConstants.IDDOC)); StructElement se = new StructElement(iddoc, doc); Locale sessionLocale = BeanUtils.getLocale(); String selectedRecordLanguage = sessionLocale.getLanguage(); try { MetadataElement md = new MetadataElement(se, sessionLocale, selectedRecordLanguage); return md; - } catch(DAOException | PresentationException e) { + } catch (DAOException | PresentationException e) { throw new IndexUnreachableException(e.getMessage()); } } @@ -611,11 +610,11 @@ public MetadataElement getMetadataElement() throws IndexUnreachableException { * @throws IndexUnreachableException */ private SolrDocument retrieveSolrDocument() throws IndexUnreachableException { - try { + try { String query = getSolrQueryForDocument(); SolrDocument doc = DataManager.getInstance().getSearchIndex().getFirstDoc(query, null); return doc; - } catch(PresentationException e) { + } catch (PresentationException e) { throw new IndexUnreachableException(e.toString()); } } @@ -626,7 +625,7 @@ private SolrDocument retrieveSolrDocument() throws IndexUnreachableException { @JsonIgnore public String getSolrQueryForDocument() { String query = "+PI_TOPSTRUCT:%s"; - if(StringUtils.isNotBlank(logId)) { + if (StringUtils.isNotBlank(logId)) { query += " +LOGID:%s"; query = String.format(query, this.pi, this.logId); } else { @@ -635,45 +634,47 @@ public String getSolrQueryForDocument() { } return query; } - + @JsonIgnore public BrowseElement getBrowseElement() throws IndexUnreachableException { - if(this.browseElement == null) { - try { + if (this.browseElement == null) { + try { SolrDocument doc = retrieveSolrDocument(); - if(doc != null) { + if (doc != null) { Locale locale = BeanUtils.getLocale(); - SearchHit sh = SearchHit.createSearchHit(doc, null, null, locale, "", null, null, null, false, null, null, SearchHit.HitType.DOCSTRCT); + SearchHit sh = SearchHit.createSearchHit(doc, null, null, locale, "", null, null, null, null, null, + SearchHit.HitType.DOCSTRCT, BeanUtils.getImageDeliveryBean().getThumbs()); this.browseElement = sh.getBrowseElement(); } - } catch(PresentationException | DAOException | ViewerConfigurationException e) { + } catch (PresentationException | DAOException | ViewerConfigurationException e) { throw new IndexUnreachableException(e.toString()); } } - + return this.browseElement; } - + @JsonIgnore public boolean isHasImages() { try { - if(this.hasImages != null) { + if (this.hasImages != null) { //no action required - } else if(this.browseElement != null) { + } else if (this.browseElement != null) { this.hasImages = this.browseElement.isHasImages(); } else { this.hasImages = isHasImagesFromSolr(); } - } catch(IndexUnreachableException | PresentationException e) { + } catch (IndexUnreachableException | PresentationException e) { logger.error("Unable to get browse element for bookmark", e); return false; } return this.hasImages; } - private boolean isHasImagesFromSolr() throws IndexUnreachableException, PresentationException { - SolrDocument doc = DataManager.getInstance().getSearchIndex().getFirstDoc(getSolrQueryForDocument(), Arrays.asList(SolrConstants.THUMBNAIL, SolrConstants.FILENAME)); + SolrDocument doc = DataManager.getInstance() + .getSearchIndex() + .getFirstDoc(getSolrQueryForDocument(), Arrays.asList(SolrConstants.THUMBNAIL, SolrConstants.FILENAME)); return SolrSearchIndex.isHasImages(doc); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSContentItem.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSContentItem.java index 80f1724cab8..64981017654 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSContentItem.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSContentItem.java @@ -300,6 +300,9 @@ public Functionality createFunctionality(CMSContentItem item) { @Transient private int nestedPagesCount = 0; + + @Transient + private Map dcStrings = null; /** * Noop constructor for javax.persistence @@ -936,6 +939,7 @@ public String getCollectionField() { public void setCollectionField(String collectionField) { this.collectionField = collectionField; this.collection = null; + this.dcStrings = null; } /** @@ -1036,16 +1040,27 @@ public void setBaseCollection(String baseCollection) { * @throws io.goobi.viewer.exceptions.IndexUnreachableException if any. */ public List getPossibleBaseCollectionList() throws IndexUnreachableException { - if (StringUtils.isBlank(collectionField)) { - return Collections.singletonList(""); + if (StringUtils.isBlank(collectionField)) { + return Collections.singletonList(""); + } + Map dcStrings = getColletionMap(); + List list = new ArrayList<>(dcStrings.keySet()); + list.add(0, ""); + Collections.sort(list); + return list; + } + + /** + * @return + * @throws IndexUnreachableException + */ + public Map getColletionMap() throws IndexUnreachableException { + if(dcStrings == null) { + dcStrings = + SearchHelper.findAllCollectionsFromField(collectionField, collectionField, getSearchPrefix(), true, true, + DataManager.getInstance().getConfiguration().getCollectionSplittingChar(collectionField)); } - Map dcStrings = - SearchHelper.findAllCollectionsFromField(collectionField, collectionField, getSearchPrefix(), true, true, - DataManager.getInstance().getConfiguration().getCollectionSplittingChar(collectionField)); - List list = new ArrayList<>(dcStrings.keySet()); - list.add(0, ""); - Collections.sort(list); - return list; + return dcStrings; } /** @@ -1058,9 +1073,7 @@ public List getPossibleIgnoreCollectionList() throws IndexUnreachableExc if (StringUtils.isBlank(collectionField)) { return Collections.singletonList(""); } - Map dcStrings = - SearchHelper.findAllCollectionsFromField(collectionField, collectionField, getSearchPrefix(), true, true, - DataManager.getInstance().getConfiguration().getCollectionSplittingChar(collectionField)); + Map dcStrings = getColletionMap(); List list = new ArrayList<>(dcStrings.keySet()); list = list.stream() .filter(c -> StringUtils.isBlank(getBaseCollection()) || c.startsWith(getBaseCollection() + ".")) @@ -1738,4 +1751,5 @@ public boolean isPaginated() { return ContentItemMode.paginated.equals(getMode()); } + } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSNavigationManager.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSNavigationManager.java index 962bf0b0e7a..b56049af339 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSNavigationManager.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSNavigationManager.java @@ -87,7 +87,7 @@ public final void loadItems() throws DAOException { addAvailableItem(searchcalendar); SelectableNavigationItem browse = new SelectableNavigationItem("browse", "browse"); addAvailableItem(browse); - SelectableNavigationItem timematrix = new SelectableNavigationItem("timematrix", "timematrix__title"); + SelectableNavigationItem timematrix = new SelectableNavigationItem("timematrix", "timematrix"); addAvailableItem(timematrix); SelectableNavigationItem statistics = new SelectableNavigationItem("statistics", "statistics"); addAvailableItem(statistics); @@ -100,6 +100,10 @@ public final void loadItems() throws DAOException { addAvailableItem(user); SelectableNavigationItem campaigns = new SelectableNavigationItem("campaigns", "admin__crowdsourcing_campaigns"); addAvailableItem(campaigns); + SelectableNavigationItem tectonics = new SelectableNavigationItem("archivetree", "archive"); + addAvailableItem(tectonics); + SelectableNavigationItem archives = new SelectableNavigationItem("archives", "archives"); + addAvailableItem(archives); addModuleItems(); addCMSPageItems(); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSPage.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSPage.java index e334c9241c0..1222e92b748 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSPage.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/CMSPage.java @@ -109,10 +109,10 @@ public class CMSPage implements Comparable, Harvestable { @Column(name = "template_id", nullable = false) private String templateId; - @Column(name = "date_created", nullable = false, columnDefinition = "TIMESTAMP(9)") + @Column(name = "date_created", nullable = false, columnDefinition = "TIMESTAMP") private LocalDateTime dateCreated; - @Column(name = "date_updated", columnDefinition = "TIMESTAMP(9)") + @Column(name = "date_updated", columnDefinition = "TIMESTAMP") private LocalDateTime dateUpdated; @Column(name = "published", nullable = false) diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/itemfunctionality/SearchFunctionality.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/itemfunctionality/SearchFunctionality.java index a1eabd02bd9..ccbcf94da04 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/itemfunctionality/SearchFunctionality.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/cms/itemfunctionality/SearchFunctionality.java @@ -31,12 +31,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.goobi.viewer.controller.DataManager; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.exceptions.ViewerConfigurationException; import io.goobi.viewer.managedbeans.SearchBean; import io.goobi.viewer.managedbeans.utils.BeanUtils; +import io.goobi.viewer.model.cms.CMSPage; import io.goobi.viewer.model.search.SearchFacets; import io.goobi.viewer.model.search.SearchFilter; import io.goobi.viewer.model.search.SearchInterface; @@ -167,7 +169,7 @@ public void searchFacetted() { * @throws io.goobi.viewer.exceptions.DAOException if any. * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ - public void search() throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { + public void search(String subtheme) throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { logger.trace("searchAction"); SearchBean searchBean = getSearchBean(); if (searchBean == null) { @@ -175,7 +177,7 @@ public void search() throws PresentationException, IndexUnreachableException, DA return; } String facetString = getSearchBean().getFacets().getCurrentFacetString(); - searchBean.getFacets().setCurrentFacetString(getCompleteFacetString(facetString)); + searchBean.getFacets().setCurrentFacetString(getCompleteFacetString(facetString, subtheme)); searchBean.search(); searchBean.getFacets().setCurrentFacetString(facetString); } @@ -183,20 +185,29 @@ public void search() throws PresentationException, IndexUnreachableException, DA /** * @return */ - private String getCompleteFacetString(String baseFacetString) { + private String getCompleteFacetString(String baseFacetString, String subtheme) { StringBuilder sb = new StringBuilder(); if (StringUtils.isNotBlank(getPageFacetString())) { String pageFacetString = getPageFacetString().replaceAll("(?i)^(AND|OR)\\s", ""); sb.append(pageFacetString); - if (StringUtils.isNotBlank(baseFacetString) && !"-".equals(baseFacetString) && !sb.toString().equals(baseFacetString)) { - sb.append(";;").append(baseFacetString); + } + if (StringUtils.isNotBlank(subtheme)) { + if(sb.length() > 0) { + sb.append(";;"); + } + String subthemeDiscriminatorField = DataManager.getInstance().getConfiguration().getSubthemeDiscriminatorField(); + sb.append(subthemeDiscriminatorField).append(":").append(subtheme); + } + if (StringUtils.isNotBlank(baseFacetString) && !"-".equals(baseFacetString)) { + if(sb.length() > 0) { + sb.append(";;"); } - } else if (StringUtils.isNotBlank(baseFacetString) && !"-".equals(baseFacetString)) { sb.append(baseFacetString); } return sb.toString(); } + /** * The part of the search url before the page number * diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/Campaign.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/Campaign.java index bac19132718..3946462567a 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/Campaign.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/Campaign.java @@ -25,6 +25,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Date; +import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -137,6 +138,20 @@ public static CampaignVisibility getByName(String name) { return null; } } + + public enum ReviewMode { + REQUIRE_REVIEW("label__require_review"), + NO_REVIEW("label__no_review"), + LIMIT_REVIEW_TO_USERGROUP("label__limit_review_to_usergroup"); + + private final String label; + private ReviewMode(String label) { + this.label = label; + } + public String getLabel() { + return this.label; + } + } private static final Logger logger = LoggerFactory.getLogger(Campaign.class); @@ -149,11 +164,11 @@ public static CampaignVisibility getByName(String name) { @Column(name = "campaign_id") private Long id; - @Column(name = "date_created", nullable = false) + @Column(name = "date_created", nullable = false, columnDefinition = "TIMESTAMP") @JsonIgnore private LocalDateTime dateCreated; - @Column(name = "date_updated") + @Column(name = "date_updated", columnDefinition = "TIMESTAMP") @JsonIgnore private LocalDateTime dateUpdated; @@ -162,11 +177,11 @@ public static CampaignVisibility getByName(String name) { @JsonIgnore private CampaignVisibility visibility = CampaignVisibility.PRIVATE; - @Column(name = "date_start") + @Column(name = "date_start", columnDefinition = "TIMESTAMP") @JsonIgnore private LocalDateTime dateStart; - @Column(name = "date_end") + @Column(name = "date_end", columnDefinition = "TIMESTAMP") @JsonIgnore private LocalDateTime dateEnd; @@ -193,6 +208,14 @@ public static CampaignVisibility getByName(String name) { @JoinColumn(name = "user_group_id") @JsonIgnore private UserGroup userGroup; + + @Column(name = "review_mode") + private ReviewMode reviewMode = ReviewMode.REQUIRE_REVIEW; + + @ManyToOne + @JoinColumn(name = "revewier_user_group_id") + @JsonIgnore + private UserGroup reviewerUserGroup; @Column(name = "time_period_enabled") @JsonIgnore @@ -284,6 +307,8 @@ public Campaign(Campaign orig) { this.timePeriodEnabled = orig.timePeriodEnabled; this.userGroup = orig.userGroup; this.limitToGroup = orig.limitToGroup; + this.reviewMode = orig.reviewMode; + this.reviewerUserGroup = orig.reviewerUserGroup; } /** @@ -569,26 +594,36 @@ public boolean isHasEnded() { */ public boolean isUserAllowedAction(User user, CampaignRecordStatus status) throws PresentationException, IndexUnreachableException, DAOException { // logger.trace("isUserAllowedAction: {}", status); - if (CampaignVisibility.PUBLIC.equals(visibility)) { - return true; - } - if (user == null || status == null) { + if (status == null) { return false; } if (!isHasStarted() || isHasEnded()) { return false; } - if (user.isSuperuser()) { + if (user != null && user.isSuperuser()) { return true; } - if (CampaignVisibility.PRIVATE.equals(visibility) && isGroupLimitActive()) { - return userGroup.getMembersAndOwner().contains(user); - } switch (status) { case ANNOTATE: - return user.isHasCrowdsourcingPrivilege(IPrivilegeHolder.PRIV_CROWDSOURCING_ANNOTATE_CAMPAIGN); + if (CampaignVisibility.PUBLIC.equals(visibility)) { + return true; + } else if(user == null) { + return false; + } else if (CampaignVisibility.PRIVATE.equals(visibility) && isGroupLimitActive()) { + return userGroup.getMembersAndOwner().contains(user); + } else { + return user.isHasCrowdsourcingPrivilege(IPrivilegeHolder.PRIV_CROWDSOURCING_ANNOTATE_CAMPAIGN); + } case REVIEW: - return user.isHasCrowdsourcingPrivilege(IPrivilegeHolder.PRIV_CROWDSOURCING_REVIEW_CAMPAIGN); + if (isReviewGroupLimitActive()) { + return user != null && reviewerUserGroup.getMembersAndOwner().contains(user); + } else if (CampaignVisibility.PUBLIC.equals(visibility)) { + return true; + } else if(user == null) { + return false; + } else { + return user.isHasCrowdsourcingPrivilege(IPrivilegeHolder.PRIV_CROWDSOURCING_REVIEW_CAMPAIGN); + } default: return false; } @@ -1459,6 +1494,14 @@ public CampaignRecordStatus getRecordStatus(String pi) { public boolean isGroupLimitActive() { return limitToGroup && userGroup != null; } + + public boolean isReviewGroupLimitActive() { + return ReviewMode.LIMIT_REVIEW_TO_USERGROUP.equals(this.reviewMode) && reviewerUserGroup != null; + } + + public boolean isReviewModeActive() { + return !ReviewMode.NO_REVIEW.equals(this.reviewMode); + } /** * Updates record status in the campaign statistics. @@ -1550,7 +1593,21 @@ public boolean isLimitToGroup() { public void setLimitToGroup(boolean limitToGroup) { this.limitToGroup = limitToGroup; } - + + /** + * @return the reviewMode + */ + public ReviewMode getReviewMode() { + return reviewMode; + } + + /** + * @param reviewMode the reviewMode to set + */ + public void setReviewMode(ReviewMode reviewMode) { + this.reviewMode = reviewMode; + } + /** * @return the userGroup */ @@ -1564,6 +1621,20 @@ public UserGroup getUserGroup() { public void setUserGroup(UserGroup userGroup) { this.userGroup = userGroup; } + + /** + * @return the reviewerUserGroup + */ + public UserGroup getReviewerUserGroup() { + return reviewerUserGroup; + } + + /** + * @param reviewerUserGroup the reviewerUserGroup to set + */ + public void setReviewerUserGroup(UserGroup reviewerUserGroup) { + this.reviewerUserGroup = reviewerUserGroup; + } /** * @return the timePeriodEnabled @@ -1636,6 +1707,13 @@ public CategorizableTranslatedSelectable getMediaItemWrapper() { return null; } - + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return getTitle(); + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/CampaignItem.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/CampaignItem.java index ea306b0be93..806a4fda211 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/CampaignItem.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/campaigns/CampaignItem.java @@ -17,7 +17,10 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -42,6 +45,7 @@ public class CampaignItem { private Campaign campaign; private CampaignRecordStatus recordStatus = null; private List log = null; + private Map> metadata = new LinkedHashMap<>(); @JsonProperty("creator") private URI creatorURI = null; @@ -175,5 +179,19 @@ public void setLog(List log) { public List getLog() { return log; } + + /** + * @return the metadata + */ + public Map> getMetadata() { + return Collections.unmodifiableMap(this.metadata); + } + + /** + * @param metadata the metadata to set + */ + public void setMetadata(Map> metadata) { + this.metadata = metadata; + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/Question.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/Question.java index 050a95fbccf..ae5b85c8b7a 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/Question.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/Question.java @@ -15,15 +15,20 @@ */ package io.goobi.viewer.model.crowdsourcing.questions; +import java.io.IOException; import java.net.URI; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Collectors; +import javax.faces.event.ValueChangeEvent; import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Convert; @@ -38,8 +43,13 @@ import javax.persistence.ManyToOne; import javax.persistence.OneToMany; import javax.persistence.Table; +import javax.persistence.Transient; +import org.apache.commons.lang3.StringUtils; +import org.apache.solr.client.solrj.SolrServerException; import org.eclipse.persistence.annotations.PrivateOwned; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; @@ -47,7 +57,11 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.dao.converter.StringListConverter; import io.goobi.viewer.dao.converter.TranslatedTextConverter; +import io.goobi.viewer.exceptions.DAOException; +import io.goobi.viewer.managedbeans.utils.BeanUtils; +import io.goobi.viewer.messages.Messages; import io.goobi.viewer.model.crowdsourcing.campaigns.Campaign; import io.goobi.viewer.model.misc.IPolyglott; import io.goobi.viewer.model.misc.TranslatedText; @@ -64,6 +78,8 @@ @JsonInclude(Include.NON_EMPTY) public class Question { + private static final Logger logger = LoggerFactory.getLogger(Question.class); + private static final String URI_ID_TEMPLATE = DataManager.getInstance().getConfiguration().getRestApiUrl() + "crowdsourcing/campaigns/{campaignId}/questions/{questionId}"; private static final String URI_ID_REGEX = ".*/crowdsourcing/campaigns/(\\d+)/questions/(\\d+)/?$"; @@ -103,6 +119,13 @@ public class Question { @Column(name = "target_frequency", nullable = false) private int targetFrequency; + @Column(name = "metadata_fields", nullable = true, columnDefinition = "LONGTEXT") + @Convert(converter = StringListConverter.class) + private List metadataFields = new ArrayList<>(); + + @Transient + private Map metadataFieldSelection = null; + /** * Empty constructor. */ @@ -143,6 +166,7 @@ public Question(Question orig) { this.targetFrequency = orig.targetFrequency; this.targetSelector = orig.targetSelector; this.text = new TranslatedText(orig.text, IPolyglott.getLocalesStatic(), IPolyglott.getCurrentLocale()); + this.metadataFields = new ArrayList<>(orig.metadataFields); } /** @@ -181,17 +205,15 @@ private void serializeTranslations() { this.translationsLegacy = Collections.emptyList(); - // Map locationsMap = this.text.map(); - // for (Entry entry : locationsMap.entrySet()) { - // Locale locale = entry.getKey(); - // String value = entry.getValue(); - // QuestionTranslation translation = translations.stream().filter(t -> t.getLanguage().equals(locale.getLanguage())).findAny() - // .orElseGet(() -> this.addTranslation(locale)); - // translation.setValue(value); - // } - // - // - // this.translations = this.text.stream().map(t -> new QuestionTranslation(t, this)).collect(Collectors.toList()); + } + + /** + * Call when metadata list changes + */ + public void serializeMetadataFields() { + if(QuestionType.METADATA.equals(getQuestionType())) { + this.metadataFields = getMetadataFieldSelection().entrySet().stream().filter(e -> e.getValue()).map(e -> e.getKey()).collect(Collectors.toList()); + } } private void deserializeTranslations() { @@ -336,7 +358,63 @@ public void setTargetSelector(TargetSelector targetSelector) { public int getTargetFrequency() { return targetFrequency; } + + /** + * @return the metadataFields + */ + public List getMetadataFields() { + return metadataFields; + } + + /** + * @param metadataFields the metadataFields to set + */ + public void setMetadataFields(List metadataFields) { + this.metadataFields = new ArrayList<>(metadataFields); + } + + public void addMetadataField(String field) { + this.metadataFields.add(field); + } + + public void removeMetadataField(String field) { + this.metadataFields.remove(field); + } + + /** + * @param metadataToAdd the metadataToAdd to set + */ + public void setMetadataToAdd(String metadataToAdd) { + if(StringUtils.isNotBlank(metadataToAdd)) { + addMetadataField(metadataToAdd); + } + } + + /** + * @return the metadataToAdd + */ + public String getMetadataToAdd() { + return ""; + } + /** + * + * @return a list of all "MD_" fields from solr + * @throws IOException + * @throws SolrServerException + */ + @JsonIgnore + public List getAvailableMetadataFields() throws DAOException { + Locale locale = BeanUtils.getLocale(); + return DataManager.getInstance().getSearchIndex().getAllFieldNames().stream() + .filter(field -> field.startsWith("MD_")) + .filter(field -> !field.endsWith("_UNTOKENIZED")) + .map(field -> field.replaceAll("_LANG_.*", "")) +// .filter(field -> !this.metadataFields.contains(field)) + .distinct() + .sorted((f1,f2) -> Messages.translate(f1, locale).compareToIgnoreCase(Messages.translate(f2, locale))) + .collect(Collectors.toList()); + } /** *

* Setter for the field targetFrequency. @@ -406,5 +484,27 @@ public NormdataAuthority getAuthorityData() { return null; } + + /** + * @return the metadataFieldSelection + * @throws IOException + * @throws SolrServerException + */ + public Map getMetadataFieldSelection() { + if(this.metadataFieldSelection == null) { + try { + this.metadataFieldSelection = getAvailableMetadataFields().stream().collect(Collectors.toMap(field -> field, field -> this.metadataFields.contains(field))); + } catch(DAOException e) { + //If the possible fields cannot be retrieved from solr, just show the already selected ones + logger.error("Failed to load all possible metadata fields " + e.toString()); + this.metadataFieldSelection = this.metadataFields.stream().collect(Collectors.toMap(field -> field, field -> Boolean.TRUE)); + } + } + return metadataFieldSelection; + } + + public List getSelectedMetadataFields() throws SolrServerException, IOException { + return this.getMetadataFieldSelection().entrySet().stream().filter(Entry::getValue).map(Entry::getKey).collect(Collectors.toList()); + } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/QuestionType.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/QuestionType.java index 99a4650dcb3..29ad6fe264d 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/QuestionType.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/crowdsourcing/questions/QuestionType.java @@ -26,10 +26,7 @@ public enum QuestionType { PLAINTEXT, RICHTEXT, GEOLOCATION_POINT, - NORMDATA - /** - * Not implemented yet DATE_PICKER, GEOLOCATION_AREA, TRANSCRIPTION, KEY_VALUE_LIST - **/ - ; + NORMDATA, + METADATA; } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/CollectionBuilder.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/CollectionBuilder.java index ac9eed50db5..e6076b46ac6 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/CollectionBuilder.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/CollectionBuilder.java @@ -46,6 +46,7 @@ import io.goobi.viewer.api.rest.v1.ApiUrls; import io.goobi.viewer.controller.DataManager; import io.goobi.viewer.controller.SolrConstants; +import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.exceptions.ViewerConfigurationException; @@ -416,7 +417,7 @@ public String getFacetField(String collectionField) { if (!DataManager.getInstance().getSearchIndex().getAllFieldNames().contains(facetField)) { facetField = collectionField; } - } catch (SolrServerException | IOException e) { + } catch (DAOException e) { logger.warn("Unable to query for facet field", e); facetField = collectionField; } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/SequenceBuilder.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/SequenceBuilder.java index 8e272b7df46..041e39ca2e9 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/SequenceBuilder.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/iiif/presentation/builder/SequenceBuilder.java @@ -95,7 +95,7 @@ public class SequenceBuilder extends AbstractBuilder { protected ImageDeliveryBean imageDelivery = BeanUtils.getImageDeliveryBean(); private BuildMode buildMode = BuildMode.IIIF; - private PageType preferredView = PageType.viewObject; + private PageType preferedView = PageType.viewObject; /** *

@@ -325,7 +325,7 @@ public Canvas generateCanvas(StructElement doc, PhysicalElement page) Sequence parent = new Sequence(getSequenceURI(doc.getPi(), null)); canvas.addWithin(parent); - LinkingContent viewerPage = new LinkingContent(new URI(getViewUrl(page, getPreferredView()))); + LinkingContent viewerPage = new LinkingContent(new URI(getViewUrl(page, getPreferedView()))); viewerPage.setLabel(new SimpleMetadataValue("goobi viewer")); canvas.addRendering(viewerPage); @@ -564,8 +564,8 @@ public SequenceBuilder setBuildMode(BuildMode buildMode) { * * @return the preferredView */ - public PageType getPreferredView() { - return preferredView; + public PageType getPreferedView() { + return preferedView; } /** @@ -576,9 +576,10 @@ public PageType getPreferredView() { * @param preferredView the preferredView to set * @return a {@link io.goobi.viewer.model.iiif.presentation.builder.SequenceBuilder} object. */ - public SequenceBuilder setPreferredView(PageType preferredView) { - this.preferredView = preferredView; + public SequenceBuilder setPreferedView(PageType preferredView) { + this.preferedView = preferredView; return this; } + } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/misc/SelectionManager.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/misc/SelectionManager.java new file mode 100644 index 00000000000..127a1eabae3 --- /dev/null +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/misc/SelectionManager.java @@ -0,0 +1,195 @@ +/** + * This file is part of the Goobi viewer - a content presentation and management application for digitized objects. + * + * Visit these websites for more information. + * - http://www.intranda.com + * - http://digiverso.com + * + * This program is free software; you can redistribute it and/or modify it 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. + * + * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along with this program. If not, see . + */ +package io.goobi.viewer.model.misc; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * @author florian + * + */ +public class SelectionManager implements Map { + + private final Map selectionMap; + + private boolean selectAll = false; + + /** + * @param collect + */ + public SelectionManager(List allEntries) { + this.selectionMap = allEntries.stream().collect(Collectors.toMap(t -> t, t -> false)); + } + + public SelectionManager() { + this.selectionMap = new HashMap<>(); + } + + /** + * @return the selectAll + */ + public boolean isSelectAll() { + return selectAll; + } + + /** + * @param selectAll the selectAll to set + */ + public void setSelectAll(boolean selectAll) { + this.selectAll = selectAll; + } + + public Boolean select(T item) { + return setSelected(item, Boolean.TRUE); + } + + public Boolean deselect(T item) { + return setSelected(item, Boolean.FALSE); + } + + public Boolean get(Object item) { + if(isSelectAll()) { + return Boolean.TRUE; + } else { + return Optional.ofNullable(selectionMap.get(item)).orElse(Boolean.FALSE); + } + } + + public Boolean put(Object item, Boolean selected) { + try { + return setSelected((T) item, selected); + } catch (ClassCastException e) { + throw new IllegalArgumentException("Key is wong type"); + } + } + + public Boolean isSelected(T item) { + return get(item); + } + + public Boolean setSelected(T item, Boolean selected) { + if(!selected) { + setSelectAll(false); + } + return selectionMap.put(item, selected); + } + + /** + * + */ + public List getAllSelected() { + if(isSelectAll()) { + return new ArrayList<>(selectionMap.keySet()); + } else { + return selectionMap.entrySet().stream().filter(e -> Boolean.TRUE.equals(e.getValue())).map(e -> e.getKey()).collect(Collectors.toList()); + } + } + + + /* (non-Javadoc) + * @see java.util.Map#clear() + */ + @Override + public void clear() { + this.setSelectAll(false); + selectionMap.clear(); + } + + /* (non-Javadoc) + * @see java.util.Map#containsKey(java.lang.Object) + */ + @Override + public boolean containsKey(Object key) { + return selectionMap.containsKey(key); + } + + /* (non-Javadoc) + * @see java.util.Map#containsValue(java.lang.Object) + */ + @Override + public boolean containsValue(Object value) { + return selectionMap.containsValue(value); + } + + /* (non-Javadoc) + * @see java.util.Map#entrySet() + */ + @Override + public Set> entrySet() { + return selectionMap.entrySet(); + } + + /* (non-Javadoc) + * @see java.util.Map#isEmpty() + */ + @Override + public boolean isEmpty() { + return selectionMap.isEmpty(); + } + + /* (non-Javadoc) + * @see java.util.Map#keySet() + */ + @Override + public Set keySet() { + return selectionMap.keySet(); + } + + /* (non-Javadoc) + * @see java.util.Map#putAll(java.util.Map) + */ + @Override + public void putAll(Map m) { + if(m.containsValue(Boolean.FALSE)) { + setSelectAll(false); + } + selectionMap.putAll(m); + } + + /* (non-Javadoc) + * @see java.util.Map#remove(java.lang.Object) + */ + @Override + public Boolean remove(Object key) { + return selectionMap.remove(key); + } + + /* (non-Javadoc) + * @see java.util.Map#size() + */ + @Override + public int size() { + return selectionMap.size(); + } + + /* (non-Javadoc) + * @see java.util.Map#values() + */ + @Override + public Collection values() { + return selectionMap.values(); + } + + + +} diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/BrowseElement.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/BrowseElement.java index 61b1f3218c0..4a009d6e5ec 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/BrowseElement.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/BrowseElement.java @@ -170,14 +170,13 @@ public class BrowseElement implements Serializable { * @param metadataList * @param locale * @param fulltext - * @param useThumbnail * @param * @throws PresentationException * @throws IndexUnreachableException * @throws DAOException * @throws ViewerConfigurationException */ - BrowseElement(StructElement structElement, List metadataList, Locale locale, String fulltext, boolean useThumbnail, + BrowseElement(StructElement structElement, List metadataList, Locale locale, String fulltext, Map> searchTerms, ThumbnailHandler thumbs) throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { this.metadataList = metadataList; diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHelper.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHelper.java index 306fff22568..a5d5b606556 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHelper.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHelper.java @@ -67,6 +67,7 @@ import io.goobi.viewer.controller.SolrConstants.DocType; import io.goobi.viewer.controller.SolrSearchIndex; import io.goobi.viewer.controller.StringTools; +import io.goobi.viewer.controller.imaging.ThumbnailHandler; import io.goobi.viewer.controller.language.LocaleComparator; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; @@ -198,6 +199,7 @@ public static List searchWithFulltext(String query, int first, int ro logger.trace("hits found: {}; results returned: {}", resp.getResults().getNumFound(), resp.getResults().size()); List ret = new ArrayList<>(resp.getResults().size()); int count = 0; + ThumbnailHandler thumbs = BeanUtils.getImageDeliveryBean().getThumbs(); for (SolrDocument doc : resp.getResults()) { logger.trace("result iddoc: {}", doc.getFieldValue(SolrConstants.IDDOC)); String fulltext = null; @@ -244,8 +246,8 @@ public static List searchWithFulltext(String query, int first, int ro } SearchHit hit = - SearchHit.createSearchHit(doc, ownerDoc, null, locale, fulltext, searchTerms, exportFields, sortFields, true, ignoreFields, - translateFields, null); + SearchHit.createSearchHit(doc, ownerDoc, null, locale, fulltext, searchTerms, exportFields, sortFields, + ignoreFields, translateFields, null, thumbs); if (keepSolrDoc) { hit.setSolrDoc(doc); } @@ -277,8 +279,9 @@ public static List searchWithFulltext(String query, int first, int ro * @throws io.goobi.viewer.exceptions.DAOException if any. * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ - public static List searchWithAggregation(String query, int first, int rows, List sortFields, List resultFields, - List filterQueries, Map params, Map> searchTerms, List exportFields, Locale locale) + public static List searchWithAggregation(String query, int first, int rows, List sortFields, + List resultFields, List filterQueries, Map params, Map> searchTerms, + List exportFields, Locale locale) throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { return searchWithAggregation(query, first, rows, sortFields, resultFields, filterQueries, params, searchTerms, exportFields, locale, false); } @@ -303,9 +306,9 @@ public static List searchWithAggregation(String query, int first, int * @throws io.goobi.viewer.exceptions.DAOException if any. * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ - public static List searchWithAggregation(String query, int first, int rows, List sortFields, List resultFields, - List filterQueries, Map params, Map> searchTerms, List exportFields, Locale locale, - boolean keepSolrDoc) + public static List searchWithAggregation(String query, int first, int rows, List sortFields, + List resultFields, List filterQueries, Map params, Map> searchTerms, + List exportFields, Locale locale, boolean keepSolrDoc) throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { logger.trace("searchWithAggregation: {}", query); QueryResponse resp = @@ -317,6 +320,7 @@ public static List searchWithAggregation(String query, int first, int Set translateFields = new HashSet<>(DataManager.getInstance().getConfiguration().getDisplayAdditionalMetadataTranslateFields()); logger.trace("hits found: {}; results returned: {}", resp.getResults().getNumFound(), resp.getResults().size()); List ret = new ArrayList<>(resp.getResults().size()); + ThumbnailHandler thumbs = BeanUtils.getImageDeliveryBean().getThumbs(); for (SolrDocument doc : resp.getResults()) { // logger.trace("result iddoc: {}", doc.getFieldValue(SolrConstants.IDDOC)); Map childDocs = resp.getExpandedResults(); @@ -324,9 +328,8 @@ public static List searchWithAggregation(String query, int first, int // Create main hit // logger.trace("Creating search hit from {}", doc); SearchHit hit = - SearchHit.createSearchHit(doc, null, null, locale, null, searchTerms, exportFields, sortFields, true, ignoreFields, - translateFields, - null); + SearchHit.createSearchHit(doc, null, null, locale, null, searchTerms, exportFields, sortFields, ignoreFields, + translateFields, null, thumbs); if (keepSolrDoc) { hit.setSolrDoc(doc); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHit.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHit.java index 5a348b46cbb..4d9e2d29d1d 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHit.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/search/SearchHit.java @@ -50,6 +50,7 @@ import io.goobi.viewer.controller.SolrConstants.DocType; import io.goobi.viewer.controller.SolrSearchIndex; import io.goobi.viewer.controller.TEITools; +import io.goobi.viewer.controller.imaging.ThumbnailHandler; import io.goobi.viewer.exceptions.CmsElementNotFoundException; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; @@ -212,10 +213,10 @@ public int compareTo(SearchHit other) { * @param searchTerms a {@link java.util.Map} object. * @param exportFields Optional fields for (Excel) export purposes. * @param sortFields - * @param useThumbnail a boolean. * @param ignoreAdditionalFields a {@link java.util.Set} object. * @param translateAdditionalFields a {@link java.util.Set} object. * @param overrideType a {@link io.goobi.viewer.model.search.SearchHit.HitType} object. + * @param thumbnailHandler * @should add export fields correctly * @return a {@link io.goobi.viewer.model.search.SearchHit} object. * @throws io.goobi.viewer.exceptions.PresentationException if any. @@ -223,11 +224,10 @@ public int compareTo(SearchHit other) { * @throws io.goobi.viewer.exceptions.DAOException if any. * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ - public static SearchHit createSearchHit(SolrDocument doc, SolrDocument ownerDoc, Set ownerAlreadyHasMetadata, Locale locale, - String fulltext, - Map> searchTerms, List exportFields, List sortFields, boolean useThumbnail, - Set ignoreAdditionalFields, - Set translateAdditionalFields, HitType overrideType) + public static SearchHit createSearchHit(SolrDocument doc, SolrDocument ownerDoc, Set ownerAlreadyHasMetadata, + Locale locale, String fulltext, Map> searchTerms, List exportFields, + List sortFields, Set ignoreAdditionalFields, Set translateAdditionalFields, + HitType overrideType, ThumbnailHandler thumbnailHandler) throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { List fulltextFragments = (fulltext == null || searchTerms == null) ? null : SearchHelper.truncateFulltext(searchTerms.get(SolrConstants.FULLTEXT), fulltext, @@ -240,8 +240,8 @@ public static SearchHit createSearchHit(SolrDocument doc, SolrDocument ownerDoc, List metadataList = DataManager.getInstance().getConfiguration().getSearchHitMetadataForTemplate(docstructType); BrowseElement browseElement = new BrowseElement(se, metadataList, locale, - (fulltextFragments != null && !fulltextFragments.isEmpty()) ? fulltextFragments.get(0) : null, useThumbnail, searchTerms, - BeanUtils.getImageDeliveryBean().getThumbs()); + (fulltextFragments != null && !fulltextFragments.isEmpty()) ? fulltextFragments.get(0) : null, searchTerms, + thumbnailHandler); // Add additional metadata fields that aren't configured for search hits but contain search term values browseElement.addAdditionalMetadataContainingSearchTerms(se, searchTerms, ignoreAdditionalFields, translateAdditionalFields); // Add sorting fields (should be added after all other metadata to avoid duplicates) @@ -498,33 +498,16 @@ public void addFulltextChild(SolrDocument doc, String language) throws IndexUnre *

* * @param number a int. + * @param skip a int. * @param locale a {@link java.util.Locale} object. * @param request a {@link javax.servlet.http.HttpServletRequest} object. + * @param thumbnailHandler * @throws io.goobi.viewer.exceptions.PresentationException if any. * @throws io.goobi.viewer.exceptions.IndexUnreachableException if any. * @throws io.goobi.viewer.exceptions.DAOException if any. * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. */ - public void populateChildren(int number, Locale locale, HttpServletRequest request) - throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { - populateChildren(number, 0, locale, request); - } - - /** - *

- * populateChildren. - *

- * - * @param number a int. - * @param locale a {@link java.util.Locale} object. - * @param request a {@link javax.servlet.http.HttpServletRequest} object. - * @param skip a int. - * @throws io.goobi.viewer.exceptions.PresentationException if any. - * @throws io.goobi.viewer.exceptions.IndexUnreachableException if any. - * @throws io.goobi.viewer.exceptions.DAOException if any. - * @throws io.goobi.viewer.exceptions.ViewerConfigurationException if any. - */ - public void populateChildren(int number, int skip, Locale locale, HttpServletRequest request) + public void populateChildren(int number, int skip, Locale locale, HttpServletRequest request, ThumbnailHandler thumbnailHandler) throws PresentationException, IndexUnreachableException, DAOException, ViewerConfigurationException { logger.trace("populateChildren START"); @@ -534,7 +517,7 @@ public void populateChildren(int number, int skip, Locale locale, HttpServletReq logger.trace("Nothing to populate"); return; } - + logger.trace("{} child hits found for {}", childDocs.size(), pi); if (number + skip > childDocs.size()) { number = childDocs.size() - skip; @@ -591,9 +574,8 @@ public void populateChildren(int number, int skip, Locale locale, HttpServletReq if (ownerHit == null) { SolrDocument ownerDoc = DataManager.getInstance().getSearchIndex().getDocumentByIddoc(ownerIddoc); if (ownerDoc != null) { - ownerHit = createSearchHit(ownerDoc, null, null, locale, fulltext, searchTerms, null, null, false, ignoreFields, - translateFields, - null); + ownerHit = createSearchHit(ownerDoc, null, null, locale, fulltext, searchTerms, null, null, ignoreFields, + translateFields, null, thumbnailHandler); children.add(ownerHit); ownerHits.put(ownerIddoc, ownerHit); ownerDocs.put(ownerIddoc, ownerDoc); @@ -607,10 +589,10 @@ public void populateChildren(int number, int skip, Locale locale, HttpServletReq } { SearchHit childHit = - createSearchHit(childDoc, ownerDocs.get(ownerIddoc), ownerHit.getBrowseElement().getExistingMetadataFields(), - locale, fulltext, searchTerms, null, null, - false, - ignoreFields, translateFields, acccessDeniedType ? HitType.ACCESSDENIED : null); + createSearchHit(childDoc, ownerDocs.get(ownerIddoc), + ownerHit.getBrowseElement().getExistingMetadataFields(), locale, fulltext, searchTerms, null, + null, ignoreFields, translateFields, acccessDeniedType ? HitType.ACCESSDENIED : null, + thumbnailHandler); // Skip grouped metadata child hits that have no additional (unique) metadata to display if (DocType.METADATA.equals(docType) && childHit.getFoundMetadata().isEmpty()) { continue; @@ -639,9 +621,8 @@ public void populateChildren(int number, int skip, Locale locale, HttpServletReq String iddoc = (String) childDoc.getFieldValue(SolrConstants.IDDOC); if (!ownerHits.containsKey(iddoc)) { SearchHit childHit = - createSearchHit(childDoc, null, null, locale, fulltext, searchTerms, null, null, false, ignoreFields, - translateFields, - null); + createSearchHit(childDoc, null, null, locale, fulltext, searchTerms, null, null, ignoreFields, + translateFields, null, thumbnailHandler); children.add(childHit); ownerHits.put(iddoc, childHit); ownerDocs.put(iddoc, childDoc); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/AccessConditionUtils.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/AccessConditionUtils.java index e2b600afdf2..514879641d4 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/AccessConditionUtils.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/AccessConditionUtils.java @@ -441,35 +441,24 @@ public static Map checkAccessPermissionByIdentiferForAllLogids( } /** - * Checks access conditions for all files in the given files list. If possible the conditions are stored in the request's session as a session - * attribute. Access is checked for general access, ip-range access, user access for both the given PI and the individual filenames if the - * relevant {@link io.goobi.viewer.model.security.LicenseType}s define a 'FILENAME' condition + * Checks if the record with the given identifier should allow access to the given request * * @param identifier The PI of the work to check * @param request The HttpRequest which may provide a {@link javax.servlet.http.HttpSession} to store the access map - * @param files The files to check access for. Must be non-empty - * @return A map of filePaths and their corresponding access rights - * @throws java.lang.SecurityException if no or an empty file list is given + * @return true if access is granted * @throws io.goobi.viewer.exceptions.IndexUnreachableException if any. * @throws io.goobi.viewer.exceptions.DAOException if any. */ - @SuppressWarnings("unchecked") - public static Map checkContentFileAccessPermission(String identifier, HttpServletRequest request, List files) + public static boolean checkContentFileAccessPermission(String identifier, HttpServletRequest request) throws IndexUnreachableException, DAOException { - if (files == null || files.isEmpty()) { - throw new SecurityException("Must provide list of file to check access for"); - } - String attributeName = IPrivilegeHolder._PRIV_PREFIX + IPrivilegeHolder.PRIV_DOWNLOAD_ORIGINAL_CONTENT; - Map accessMap = new HashMap<>(); + Boolean access = false; if (request != null && request.getSession() != null) { try { - accessMap = (Map) request.getSession().getAttribute(attributeName); - if (accessMap != null && accessMap.keySet().containsAll(files.stream().map(Path::toString).collect(Collectors.toList()))) { - return accessMap; - } else if (accessMap == null) { - accessMap = new HashMap<>(); + access = (Boolean) request.getSession().getAttribute(attributeName); + if (access != null) { + return access; } } catch (ClassCastException e) { logger.error("Cannot cast session attribute '" + attributeName + "' to Map", e); @@ -479,8 +468,8 @@ public static Map checkContentFileAccessPermission(String ident // logger.trace("checkContentFileAccessPermission({})", identifier); if (StringUtils.isNotEmpty(identifier)) { try { - String query = "+" + SolrConstants.PI + ":" + identifier; Set requiredAccessConditions = new HashSet<>(); + String query = "+" + SolrConstants.PI + ":" + identifier; SolrDocumentList results = DataManager.getInstance() .getSearchIndex() .search(query, 1, null, Arrays.asList(new String[] { SolrConstants.ACCESSCONDITION })); @@ -496,30 +485,17 @@ public static Map checkContentFileAccessPermission(String ident } } - User user = BeanUtils.getUserFromRequest(request); - if (user == null) { - UserBean userBean = BeanUtils.getUserBean(); - if (userBean != null) { - user = userBean.getUser(); - } - } + access = checkAccessPermission(requiredAccessConditions, IPrivilegeHolder.PRIV_DOWNLOAD_ORIGINAL_CONTENT, query, request); - Map lokalAccessMap = - checkAccessPermission(DataManager.getInstance().getDao().getRecordLicenseTypes(), requiredAccessConditions, - IPrivilegeHolder.PRIV_DOWNLOAD_ORIGINAL_CONTENT, user, NetTools.getIpAddress(request), query, files); - accessMap.putAll(lokalAccessMap); } catch (PresentationException e) { logger.debug("PresentationException thrown here: {}", e.getMessage()); } } if (request != null && request.getSession() != null) { - request.getSession().setAttribute(attributeName, accessMap); + request.getSession().setAttribute(attributeName, access); } //return only the access status for the relevant files - return accessMap.entrySet() - .stream() - .filter(entry -> files.contains(Paths.get(entry.getKey()))) - .collect(Collectors.toMap(Entry::getKey, Entry::getValue)); + return access; } /** @@ -769,7 +745,7 @@ public static boolean checkAccessPermissionByIdentifierAndFileNameWithSessionMap */ public static boolean checkAccessPermission(List allLicenseTypes, Set requiredAccessConditions, String privilegeName, User user, String remoteAddress, String query) throws IndexUnreachableException, PresentationException, DAOException { - return !checkAccessPermission(allLicenseTypes, requiredAccessConditions, privilegeName, user, remoteAddress, query, null).values() + return !checkAccessPermissions(allLicenseTypes, requiredAccessConditions, privilegeName, user, remoteAddress, query).values() .contains(Boolean.FALSE); } @@ -796,17 +772,14 @@ public static boolean checkAccessPermission(List allLicenseTypes, S * @should return true if ip range allows access * @should not return true if no ip range matches */ - static Map checkAccessPermission(List allLicenseTypes, Set requiredAccessConditions, - String privilegeName, User user, String remoteAddress, String query, List files) + static Map checkAccessPermissions(List allLicenseTypes, Set requiredAccessConditions, + String privilegeName, User user, String remoteAddress, String query) throws IndexUnreachableException, PresentationException, DAOException { // logger.trace("checkAccessPermission({},{})", requiredAccessConditions, privilegeName); Map accessMap = new HashMap<>(); - if (files != null) { - files.forEach(file -> accessMap.put(file.toString(), Boolean.FALSE)); - } else { - accessMap.put("", Boolean.FALSE); - } + accessMap.put("", Boolean.FALSE); + // If user is superuser, allow immediately if (user != null && user.isSuperuser()) { accessMap.keySet().forEach(key -> accessMap.put(key, Boolean.TRUE)); @@ -887,7 +860,6 @@ static Map checkAccessPermission(List allLicenseTy } // If not within an allowed IP range, check the current user's satisfied access conditions - if (user != null && user.canSatisfyAllAccessConditions(requiredAccessConditions, privilegeName, null)) { accessMap.put(key, Boolean.TRUE); } @@ -975,30 +947,14 @@ static Map> getRelevantLicenseTypesOnly(List types = ret.get(path); - if (types == null) { - types = new ArrayList<>(); - ret.put(path, types); - } - if (Pattern.matches(licenseType.getFilenameConditions(), Paths.get(path).getFileName().toString())) { - types.add(licenseType); - } - } - } - } else { - //no individual file conditions. Write same licenseTypes for all files - for (String key : accessMap.keySet()) { - List types = ret.get(key); - if (types == null) { - types = new ArrayList<>(); - ret.put(key, types); - } - types.add(licenseType); + //no individual file conditions. Write same licenseTypes for all files + for (String key : accessMap.keySet()) { + List types = ret.get(key); + if (types == null) { + types = new ArrayList<>(); + ret.put(key, types); } + types.add(licenseType); } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/License.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/License.java index 859b510bc6b..ef29087c268 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/License.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/License.java @@ -128,10 +128,10 @@ public boolean equals(Object obj) { @JoinColumn(name = "ip_range_id") private IpRange ipRange; - @Column(name = "date_start") + @Column(name = "date_start", columnDefinition = "TIMESTAMP") private LocalDateTime start; - @Column(name = "date_end") + @Column(name = "date_end", columnDefinition = "TIMESTAMP") private LocalDateTime end; @ElementCollection(fetch = FetchType.EAGER) diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/LicenseType.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/LicenseType.java index fbe48c15573..24da91c2e5a 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/LicenseType.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/LicenseType.java @@ -77,8 +77,6 @@ public class LicenseType implements IPrivilegeHolder, ILicenseType { public static final String LICENSE_TYPE_CROWDSOURCING_CAMPAIGNS = "licenseType_crowdsourcing_campaigns"; private static final String LICENSE_TYPE_DESC_CROWDSOURCING_CAMPAIGNS = "licenseType_crowdsourcing_campaigns_desc"; - // private static final String CONDITIONS_QUERY = "QUERY:\\{(.*?)\\}"; - private static final String CONDITIONS_FILENAME = "FILENAME:\\{(.*)\\}"; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -270,17 +268,6 @@ public String getProcessedConditions() { return conditions.trim(); } - /** - *

- * getFilenameConditions. - *

- * - * @return a {@link java.lang.String} object. - */ - public String getFilenameConditions() { - return getFilenameConditions(this.conditions); - } - /** * Get the conditions referring to a SOLR query. This is either the substring in {} after QUERY: or the entire string if neither QUERY:{...} nor * FILENAME:{...} is part of the given string @@ -289,11 +276,7 @@ public String getFilenameConditions() { * @return */ private String getQueryConditions(String conditions) { - String filenameConditions = getMatch(conditions, CONDITIONS_FILENAME); - String queryConditions = conditions == null ? "" : conditions.replaceAll(CONDITIONS_FILENAME, ""); - if (StringUtils.isBlank(queryConditions) && StringUtils.isBlank(filenameConditions)) { - return conditions == null ? "" : conditions; - } + String queryConditions = conditions == null ? "" : conditions; return queryConditions; } @@ -317,18 +300,6 @@ public String getMatch(String conditions, String pattern) { return ""; } - /** - * Get the conditions referring to filename matching. This is either the substring in {} after FILENAME: or null if neither QUERY:{...} nor - * FILENAME:{...} is part of the given string - * - * @param conditions - * @return - */ - private String getFilenameConditions(String conditions) { - String filenameConditions = getMatch(conditions, CONDITIONS_FILENAME); - return filenameConditions; - } - /** *

* isCmsType. diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/user/User.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/user/User.java index 30fcd8f6f1d..4698424937f 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/user/User.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/security/user/User.java @@ -123,8 +123,7 @@ public class User implements ILicensee, HttpSessionBindingListener, Serializable @Column(name = "activation_key") private String activationKey; - // @Temporal(TemporalType.TIMESTAMP) - @Column(name = "last_login") + @Column(name = "last_login", columnDefinition = "TIMESTAMP") private LocalDateTime lastLogin; @Column(name = "active", nullable = false) diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusJob.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusJob.java index bd9ca3c9a0d..ab9a3381fbd 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusJob.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusJob.java @@ -106,7 +106,7 @@ public static JobStatus getByName(String name) { * subsequent requests. This + TTL is the time of expiration. */ // @Temporal(TemporalType.TIMESTAMP) - @Column(name = "date_created", nullable = false) + @Column(name = "date_created", nullable = false, columnDefinition = "TIMESTAMP") private LocalDateTime dateCreated; @Enumerated(EnumType.STRING) diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/TranskribusUtils.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusUtils.java similarity index 97% rename from goobi-viewer-core/src/main/java/io/goobi/viewer/controller/TranskribusUtils.java rename to goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusUtils.java index 9f3705c997b..80d9c45b2d0 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/controller/TranskribusUtils.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/transkribus/TranskribusUtils.java @@ -13,7 +13,7 @@ * * You should have received a copy of the GNU General Public License along with this program. If not, see . */ -package io.goobi.viewer.controller; +package io.goobi.viewer.model.transkribus; import java.io.IOException; import java.net.URLEncoder; @@ -31,11 +31,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import io.goobi.viewer.controller.DataManager; +import io.goobi.viewer.controller.NetTools; +import io.goobi.viewer.controller.StringTools; +import io.goobi.viewer.controller.XmlTools; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.HTTPException; -import io.goobi.viewer.model.transkribus.TranskribusJob; import io.goobi.viewer.model.transkribus.TranskribusJob.JobStatus; -import io.goobi.viewer.model.transkribus.TranskribusSession; /** *

@@ -97,7 +99,7 @@ public static TranskribusJob ingestRecord(String restApiUrl, TranskribusSession DataManager.getInstance().getConfiguration().getTranskribusDefaultCollection()); if (userCollectionId == null) { logger.error("Could not create add collection '{}' to the user acccount '{}'.", - DataManager.getInstance().getConfiguration().getDefaultCollection(), userSession.getUserName()); + DataManager.getInstance().getConfiguration().getTranskribusDefaultCollection(), userSession.getUserName()); return null; } } @@ -110,13 +112,13 @@ public static TranskribusJob ingestRecord(String restApiUrl, TranskribusSession // Check and create the default viewer instance collection String viewerCollectionId = - getCollectionId(restApiUrl, viewerSession.getSessionId(), DataManager.getInstance().getConfiguration().getDefaultCollection()); + getCollectionId(restApiUrl, viewerSession.getSessionId(), DataManager.getInstance().getConfiguration().getTranskribusDefaultCollection()); if (viewerCollectionId == null) { viewerCollectionId = - createCollection(restApiUrl, viewerSession.getSessionId(), DataManager.getInstance().getConfiguration().getDefaultCollection()); + createCollection(restApiUrl, viewerSession.getSessionId(), DataManager.getInstance().getConfiguration().getTranskribusDefaultCollection()); if (viewerCollectionId == null) { logger.error("Could not create the default collection '{}' for the viewer instance.", - DataManager.getInstance().getConfiguration().getDefaultCollection()); + DataManager.getInstance().getConfiguration().getTranskribusDefaultCollection()); return null; } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/LabeledLink.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/LabeledLink.java index f49304ea113..f8e6e06a14f 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/LabeledLink.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/LabeledLink.java @@ -32,10 +32,19 @@ public class LabeledLink implements Serializable { private static final long serialVersionUID = -2546718627110716169L; + public static final LabeledLink EMPTY = new LabeledLink(); + protected IMetadataValue name; protected String url; protected int weight; + /** + * Internal constructor for empty value + */ + private LabeledLink() { + + } + /** *

* Constructor for LabeledLink. diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/PageType.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/PageType.java index 61e5182be30..ea762a48040 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/PageType.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/PageType.java @@ -50,6 +50,9 @@ public enum PageType { firstWorkInCollection("rest/redirect/toFirstWork"), sitelinks("sitelinks"), user("user"), + archive("archive"), + archives("archives"), + timematrix("timematrix"), //admin admin("admin"), adminUsers("admin/users"), diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/ViewManager.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/ViewManager.java index 83ab7ce946a..fe811a87109 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/ViewManager.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/model/viewer/ViewManager.java @@ -16,7 +16,6 @@ package io.goobi.viewer.model.viewer; import java.awt.Dimension; -import java.io.File; import java.io.IOException; import java.io.Serializable; import java.io.UnsupportedEncodingException; @@ -26,12 +25,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; +import java.util.stream.Stream; import javax.faces.context.FacesContext; import javax.faces.event.ValueChangeEvent; @@ -50,13 +48,13 @@ import io.goobi.viewer.api.rest.AbstractApiUrlManager; import io.goobi.viewer.api.rest.v1.ApiUrls; import io.goobi.viewer.controller.AlphanumCollatorComparator; +import io.goobi.viewer.controller.Configuration; import io.goobi.viewer.controller.DataFileTools; import io.goobi.viewer.controller.DataManager; import io.goobi.viewer.controller.FileTools; import io.goobi.viewer.controller.SolrConstants; import io.goobi.viewer.controller.SolrSearchIndex; import io.goobi.viewer.controller.StringTools; -import io.goobi.viewer.controller.TranskribusUtils; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.HTTPException; import io.goobi.viewer.exceptions.IDDOCNotFoundException; @@ -80,6 +78,7 @@ import io.goobi.viewer.model.toc.TOC; import io.goobi.viewer.model.transkribus.TranskribusJob; import io.goobi.viewer.model.transkribus.TranskribusSession; +import io.goobi.viewer.model.transkribus.TranskribusUtils; import io.goobi.viewer.model.viewer.pageloader.IPageLoader; import io.goobi.viewer.model.viewer.pageloader.LeanPageLoader; @@ -95,7 +94,7 @@ public class ViewManager implements Serializable { private ImageDeliveryBean imageDeliveryBean; /** IDDOC of the top level document. */ - private long topDocumentIddoc; + private final long topDocumentIddoc; /** IDDOC of the current level document. The initial top level document values eventually gets overridden with the image owner element's IDDOC. */ private long currentDocumentIddoc; /** LOGID of the current level document. */ @@ -142,6 +141,7 @@ public class ViewManager implements Serializable { private Long pagesWithAlto = null; private Boolean workHasTEIFiles = null; private Boolean metadataViewOnly = null; + private List downloadFilenames = null; /** *

@@ -181,7 +181,7 @@ public ViewManager(StructElement topDocument, IPageLoader pageLoader, long curre currentThumbnailPage = 1; // annotationManager = new AnnotationManager(topDocument); pi = topDocument.getPi(); - + if (!topDocument.isAnchor()) { // Generate drop-down page selector elements dropdownPages.clear(); @@ -1440,7 +1440,7 @@ public String getImagesSizeThumbnail() throws IndexUnreachableException { Double im = (double) pageLoader.getNumPages(); Double thumb = (double) DataManager.getInstance().getConfiguration().getViewerThumbnailsPerPage(); - int answer = new Double(Math.floor(im / thumb)).intValue(); + int answer = Double.valueOf(Math.floor(im / thumb)).intValue(); if (im % thumb != 0 || answer == 0) { answer++; } @@ -2502,6 +2502,35 @@ public int getZoomSlider() { return this.zoomSlider; } + /** + * List all files in {@link Configuration#getOrigContentFolder()} for which accecss is granted and which are not hidden per config + * + * @return the list of downloadable filenames. If no downloadable resources exists, an empty list is returned + * @throws PresentationException + * @throws IndexUnreachableException + * @throws DAOException + * @throws IOException + */ + private List listDownloadableContent() throws PresentationException, IndexUnreachableException, DAOException, IOException { + // if (this.downloadFilenames == null) { + Path sourceFileDir = DataFileTools.getDataFolder(pi, DataManager.getInstance().getConfiguration().getOrigContentFolder()); + if (Files.exists(sourceFileDir) && AccessConditionUtils.checkContentFileAccessPermission(pi, + (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest())) { + String hideDownloadFilesRegex = DataManager.getInstance().getConfiguration().getHideDownloadFileRegex(); + try (Stream files = Files.list(sourceFileDir)) { + Stream filenames = files.map(path -> path.getFileName().toString()); + if (StringUtils.isNotEmpty(hideDownloadFilesRegex)) { + filenames = filenames.filter(filename -> !filename.matches(hideDownloadFilesRegex)); + } + this.downloadFilenames = filenames.collect(Collectors.toList()); + } + } else { + this.downloadFilenames = Collections.emptyList(); + } + // } + return this.downloadFilenames; + } + /** * Returns true if original content download has been enabled in the configuration and there are files in the original content folder for this * record. @@ -2509,29 +2538,13 @@ public int getZoomSlider() { * @return a boolean. */ public boolean isDisplayContentDownloadMenu() { - if (!DataManager.getInstance().getConfiguration().isOriginalContentDownload()) { + if (!DataManager.getInstance().getConfiguration().isDisplaySidebarWidgetDownload()) { return false; } - try { - Path sourceFileDir = DataFileTools.getDataFolder(pi, DataManager.getInstance().getConfiguration().getOrigContentFolder()); - if (!Files.isDirectory(sourceFileDir)) { - return false; - } - - List files = Arrays.asList(sourceFileDir.toFile().listFiles()).stream().map(File::toPath).collect(Collectors.toList()); - if (!files.isEmpty()) { - return AccessConditionUtils - .checkContentFileAccessPermission(pi, - (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest(), files) - .containsValue(Boolean.TRUE); - } - } catch (IndexUnreachableException e) { - logger.debug("IndexUnreachableException thrown here: {}", e.getMessage()); - } catch (DAOException e) { - logger.debug("DAOException thrown here: {}", e.getMessage()); - } catch (PresentationException e) { - logger.debug("PresentationException thrown here: {}", e.getMessage()); + return !listDownloadableContent().isEmpty(); + } catch (PresentationException | IndexUnreachableException | DAOException | IOException e) { + logger.warn("Error listing downloadable content: " + e.toString()); } return false; @@ -2544,52 +2557,31 @@ public boolean isDisplayContentDownloadMenu() { * @throws io.goobi.viewer.exceptions.IndexUnreachableException if any. * @throws io.goobi.viewer.exceptions.DAOException if any. * @throws io.goobi.viewer.exceptions.PresentationException if any. + * @throws IOException */ - public List getContentDownloadLinksForWork() throws IndexUnreachableException, DAOException, PresentationException { - Path sourceFileDir = DataFileTools.getDataFolder(pi, DataManager.getInstance().getConfiguration().getOrigContentFolder()); - if (!Files.isDirectory(sourceFileDir)) { - return Collections.emptyList(); - } - - List ret = new ArrayList<>(); - try { - File[] sourcesArray = sourceFileDir.toFile().listFiles(); - if (sourcesArray != null && sourcesArray.length > 0) { - List sourcesList = Arrays.asList(sourcesArray); - Collections.sort(sourcesList, filenameComparator); - Map fileAccess = AccessConditionUtils.checkContentFileAccessPermission(getPi(), BeanUtils.getRequest(), - sourcesList.stream().map(file -> file.toPath()).collect(Collectors.toList())); - AbstractApiUrlManager apiUrls = DataManager.getInstance().getRestApiManager().getContentApiManager(); - for (File file : sourcesList) { - if (file.isFile()) { - Boolean access = fileAccess.get(file.toPath().toString()); - if (Boolean.TRUE.equals(access)) { - String pi = getPi(); - String filename = URLEncoder.encode(file.getName(), StringTools.DEFAULT_ENCODING); - String url = apiUrls.path(ApiUrls.RECORDS_FILES, ApiUrls.RECORDS_FILES_SOURCE).params(pi, filename).build(); - ret.add(new LabeledLink(file.getName(), url, 0)); - } - ; - } - } - } - } catch (UnsupportedEncodingException e) { - logger.error(e.getMessage(), e); - } + public List getContentDownloadLinksForWork() throws IOException, PresentationException, IndexUnreachableException, DAOException { + AlphanumCollatorComparator comparator = new AlphanumCollatorComparator(null); + List links = listDownloadableContent().stream() + .sorted(comparator) + .map(this::getLinkToDownloadFile) + .filter(link -> link != LabeledLink.EMPTY) + .collect(Collectors.toList()); + return links; - return ret; } - Comparator filenameComparator = new Comparator() { - AlphanumCollatorComparator comparator = new AlphanumCollatorComparator(null); - - @Override - public int compare(File f1, File f2) { - return comparator.compare(f1.getName(), f2.getName()); - // return f1.getName().compareTo(f2.getName()); + private LabeledLink getLinkToDownloadFile(String filename) { + try { + String pi = getPi(); + AbstractApiUrlManager apiUrls = DataManager.getInstance().getRestApiManager().getContentApiManager(); + String filenameEncoded = URLEncoder.encode(filename, StringTools.DEFAULT_ENCODING); + String url = apiUrls.path(ApiUrls.RECORDS_FILES, ApiUrls.RECORDS_FILES_SOURCE).params(pi, filenameEncoded).build(); + return new LabeledLink(filename, url, 0); + } catch (UnsupportedEncodingException | IndexUnreachableException e) { + logger.error("Failed to create download link to " + filename, e); + return LabeledLink.EMPTY; } - - }; + } /** *

@@ -2602,23 +2594,12 @@ public long getTopDocumentIddoc() { return topDocumentIddoc; } - /** - *

- * Setter for the field topDocumentIddoc. - *

- * - * @param topDocumentIddoc the topDocumentIddoc to set - */ - public void setTopDocumentIddoc(long topDocumentIddoc) { - this.topDocumentIddoc = topDocumentIddoc; - } - public Long getAnchorDocumentIddoc() { if (this.anchorDocument != null) { return anchorDocument.getLuceneId(); - } else { - return null; } + + return null; } /** @@ -3417,6 +3398,19 @@ public boolean isDisplayCiteLinkPage() throws IndexUnreachableException, DAOExce return getCurrentPage() != null; } + /** + * + * @return + */ + public String getArchiveEntryIdentifier() { + if (topDocument == null) { + return null; + } + + logger.trace("getArchiveEntryIdentifier: {}", topDocument.getMetadataValue(SolrConstants.ARCHIVE_ENTRY_ID)); + return topDocument.getMetadataValue(SolrConstants.ARCHIVE_ENTRY_ID); + } + /** * Creates an instance of ViewManager loaded with the record with the given identifier. * diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/WebApiServlet.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/WebApiServlet.java index 55f59ce7bee..65ada1ae30f 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/WebApiServlet.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/WebApiServlet.java @@ -21,7 +21,6 @@ import java.util.Collections; import java.util.LinkedList; import java.util.List; -import java.util.Random; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; @@ -39,10 +38,13 @@ import io.goobi.viewer.controller.DataManager; import io.goobi.viewer.controller.JsonTools; import io.goobi.viewer.controller.SolrConstants; +import io.goobi.viewer.controller.SolrSearchIndex; +import io.goobi.viewer.controller.imaging.ThumbnailHandler; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.exceptions.ViewerConfigurationException; +import io.goobi.viewer.managedbeans.utils.BeanUtils; import io.goobi.viewer.model.metadata.CompareYearSolrDocWrapper; import io.goobi.viewer.model.search.SearchHelper; import io.goobi.viewer.model.viewer.StringPair; @@ -76,7 +78,6 @@ public WebApiServlet() { /** {@inheritDoc} */ @Override - @SuppressWarnings("unchecked") protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action = null; String encoding = "utf-8"; @@ -160,9 +161,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t JSONArray jsonArray = new JSONArray(); try { - // Solr supports dynamic random_* sorting fields. Each value represents one particular order, so a random number is required. - Random random = new Random(); - String sortfield = new StringBuilder().append("random_").append(random.nextInt(Integer.MAX_VALUE)).toString(); + String sortfield = SolrSearchIndex.generateRandomSortField(); SolrDocumentList result = DataManager.getInstance() .getSearchIndex() .search(query, 0, count, Collections.singletonList(new StringPair(sortfield, "asc")), null, null) @@ -176,9 +175,11 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t } Collections.sort(sortDocResult); + ThumbnailHandler thumbs = BeanUtils.getImageDeliveryBean().getThumbs(); for (CompareYearSolrDocWrapper solrWrapper : sortDocResult) { SolrDocument doc = solrWrapper.getSolrDocument(); - JSONObject jsonObj = JsonTools.getRecordJsonObject(doc, ServletUtils.getServletPathWithHostAsUrlFromRequest(request)); + JSONObject jsonObj = + JsonTools.getRecordJsonObject(doc, ServletUtils.getServletPathWithHostAsUrlFromRequest(request), thumbs); jsonArray.put(jsonObj); } } catch (PresentationException e) { @@ -260,9 +261,7 @@ protected void doGet(HttpServletRequest request, HttpServletResponse response) t if (randomize) { sortFields.clear(); - // Solr supports dynamic random_* sorting fields. Each value represents one particular order, so a random number is required. - Random random = new Random(); - sortFields.add(new StringPair("random_" + random.nextInt(Integer.MAX_VALUE), sortOrderString)); + sortFields.add(new StringPair(SolrSearchIndex.generateRandomSortField(), sortOrderString)); logger.trace("sortFields: {}", sortFields); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/oembed/RichOEmbedResponse.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/oembed/RichOEmbedResponse.java index b133675ad4e..cc07290591c 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/oembed/RichOEmbedResponse.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/oembed/RichOEmbedResponse.java @@ -74,6 +74,7 @@ private void generateHtml(OEmbedRecord record, int width, int height) throws Vie sb.append(" src='"); sb.append(record.getUri()); sb.append("'"); + sb.append(" title='Map'"); sb.append(" width='"); sb.append(width); sb.append("'"); diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/NormdataResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/NormdataResource.java index f16c2c4fa2e..380b4b42b77 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/NormdataResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/NormdataResource.java @@ -48,6 +48,7 @@ import io.goobi.viewer.api.rest.ViewerRestServiceBinding; import io.goobi.viewer.controller.DataManager; import io.goobi.viewer.controller.StringTools; +import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.managedbeans.utils.BeanUtils; import io.goobi.viewer.messages.ViewerResourceBundle; @@ -95,13 +96,14 @@ protected NormdataResource(HttpServletRequest request) { * @throws java.net.MalformedURLException if any. * @throws de.unigoettingen.sub.commons.contentlib.exceptions.ContentNotFoundException if any. * @throws de.unigoettingen.sub.commons.contentlib.exceptions.ServiceNotAllowedException if any. + * @throws PresentationException */ @GET @Path("/get/{url}/{template}/{lang}") @Produces({ MediaType.APPLICATION_JSON }) @CORSBinding public String getNormData(@PathParam("url") String url, @PathParam("template") String template, @PathParam("lang") String lang) - throws MalformedURLException, ContentNotFoundException, ServiceNotAllowedException { + throws MalformedURLException, ContentNotFoundException, ServiceNotAllowedException, PresentationException { logger.trace("getNormData: {}", url); if (servletResponse != null) { servletResponse.setCharacterEncoding(StringTools.DEFAULT_ENCODING); @@ -186,8 +188,12 @@ public String getNormData(@PathParam("url") String url, @PathParam("template") S jsonArray.put(addNormDataValuesToJSON(normData, locale)); } } - - return jsonArray.toString(); + + try { + return jsonArray.toString(); + } catch(NoSuchMethodError e) { + throw new PresentationException("Error creating json of normdata from " + url); + } } /** @@ -251,12 +257,13 @@ static JSONObject addNormDataValuesToJSON(NormData normData, Locale locale) { * @throws java.net.MalformedURLException if any. * @throws de.unigoettingen.sub.commons.contentlib.exceptions.ContentNotFoundException if any. * @throws de.unigoettingen.sub.commons.contentlib.exceptions.ServiceNotAllowedException if any. + * @throws PresentationException */ @GET @Path("/get/{url}/{lang}") @Produces({ MediaType.APPLICATION_JSON }) public String getNormData(@PathParam("url") String url, @PathParam("lang") String lang) - throws MalformedURLException, ContentNotFoundException, ServiceNotAllowedException { + throws MalformedURLException, ContentNotFoundException, ServiceNotAllowedException, PresentationException { return getNormData(url, "_DEFAULT", lang); } } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/RecordsResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/RecordsResource.java index c800ae7470e..01757cdf049 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/RecordsResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/content/RecordsResource.java @@ -22,7 +22,6 @@ import java.nio.file.Paths; import java.util.Collections; import java.util.LinkedList; -import java.util.Random; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -58,11 +57,13 @@ import io.goobi.viewer.controller.SolrConstants; import io.goobi.viewer.controller.SolrSearchIndex; import io.goobi.viewer.controller.StringTools; +import io.goobi.viewer.controller.imaging.ThumbnailHandler; import io.goobi.viewer.exceptions.DAOException; import io.goobi.viewer.exceptions.IndexUnreachableException; import io.goobi.viewer.exceptions.PresentationException; import io.goobi.viewer.exceptions.RecordNotFoundException; import io.goobi.viewer.exceptions.ViewerConfigurationException; +import io.goobi.viewer.managedbeans.utils.BeanUtils; import io.goobi.viewer.model.metadata.CompareYearSolrDocWrapper; import io.goobi.viewer.model.metadata.MetadataTools; import io.goobi.viewer.model.search.SearchHelper; @@ -150,9 +151,7 @@ public String getTimeMatrix(@PathParam("query") String query, @PathParam("count" logger.trace("count: {}", count); JSONArray jsonArray = new JSONArray(); - // Solr supports dynamic random_* sorting fields. Each value represents one particular order, so a random number is required. - Random random = new Random(); - String sortfield = new StringBuilder().append("random_").append(random.nextInt(Integer.MAX_VALUE)).toString(); + String sortfield = SolrSearchIndex.generateRandomSortField(); SolrDocumentList result = DataManager.getInstance() .getSearchIndex() .search(query, 0, count, Collections.singletonList(new StringPair(sortfield, "asc")), null, null) @@ -166,9 +165,11 @@ public String getTimeMatrix(@PathParam("query") String query, @PathParam("count" } Collections.sort(sortDocResult); + ThumbnailHandler thumbs = BeanUtils.getImageDeliveryBean().getThumbs(); for (CompareYearSolrDocWrapper solrWrapper : sortDocResult) { SolrDocument doc = solrWrapper.getSolrDocument(); - JSONObject jsonObj = JsonTools.getRecordJsonObject(doc, ServletUtils.getServletPathWithHostAsUrlFromRequest(servletRequest)); + JSONObject jsonObj = + JsonTools.getRecordJsonObject(doc, ServletUtils.getServletPathWithHostAsUrlFromRequest(servletRequest), thumbs); jsonArray.put(jsonObj); } diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/search/SearchResultResource.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/search/SearchResultResource.java index 594ab566117..166d7625910 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/search/SearchResultResource.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/servlets/rest/search/SearchResultResource.java @@ -88,7 +88,8 @@ public SearchHitChildList getTagsForPageJson(@PathParam("id") String hitId, @Pat for (SearchHit searchHit : searchHits) { if (hitId.equals(Long.toString(searchHit.getBrowseElement().getIddoc()))) { if (searchHit.getHitsPopulated() < numChildren) { - searchHit.populateChildren(numChildren - searchHit.getHitsPopulated(), searchHit.getHitsPopulated(), locale, servletRequest); + searchHit.populateChildren(numChildren - searchHit.getHitsPopulated(), searchHit.getHitsPopulated(), locale, + servletRequest, BeanUtils.getImageDeliveryBean().getThumbs()); } Collections.sort(searchHit.getChildren()); SearchHitChildList searchHitChildren = diff --git a/goobi-viewer-core/src/main/java/io/goobi/viewer/websockets/UserEndpoint.java b/goobi-viewer-core/src/main/java/io/goobi/viewer/websockets/UserEndpoint.java index 14d68bd75f9..51c6c857220 100644 --- a/goobi-viewer-core/src/main/java/io/goobi/viewer/websockets/UserEndpoint.java +++ b/goobi-viewer-core/src/main/java/io/goobi/viewer/websockets/UserEndpoint.java @@ -48,10 +48,10 @@ public class UserEndpoint { @OnOpen public void onOpen(Session session, EndpointConfig config) { - logger.trace("onOpen: {}", session.getId()); + // logger.trace("onOpen: {}", session.getId()); this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName()); if (httpSession != null) { - logger.trace("HTTP session ID: {}", httpSession.getId()); + // logger.trace("HTTP session ID: {}", httpSession.getId()); cancelClearTimer(httpSession.getId()); } } @@ -66,7 +66,7 @@ public void onMessage(String message) { @OnClose public void onClose(Session session) { - logger.trace("onClose {}", session.getId()); + // logger.trace("onClose {}", session.getId()); if (httpSession != null) { delayedRemoveLocksForSessionId(httpSession.getId(), 30000L); } @@ -74,7 +74,7 @@ public void onClose(Session session) { @OnError public void onError(Session session, Throwable t) { - logger.error(t.getMessage()); + logger.warn(t.getMessage()); } /** @@ -105,17 +105,17 @@ private static void delayedRemoveLocksForSessionId(String sessionId, long delay) return; } - logger.trace("Starting timer for {}", sessionId); + // logger.trace("Starting timer for {}", sessionId); TimerTask task = new TimerTask() { @Override public void run() { if (sessionClearTimers.containsKey(sessionId)) { int count = DataManager.getInstance().getRecordLockManager().removeLocksForSessionId(sessionId, null); - logger.trace("Removed {} record locks for session '{}'.", count, sessionId); + // logger.trace("Removed {} record locks for session '{}'.", count, sessionId); sessionClearTimers.remove(sessionId); } else { - logger.trace("Session {} has been refreshed and won't be cleared", sessionId); + // logger.trace("Session {} has been refreshed and won't be cleared", sessionId); } } }; diff --git a/goobi-viewer-core/src/main/resources/META-INF/persistence.xml b/goobi-viewer-core/src/main/resources/META-INF/persistence.xml index 6cb5cbc4216..b0f13b97a56 100644 --- a/goobi-viewer-core/src/main/resources/META-INF/persistence.xml +++ b/goobi-viewer-core/src/main/resources/META-INF/persistence.xml @@ -49,6 +49,7 @@ io.goobi.viewer.dao.converter.TranslatedTextConverter io.goobi.viewer.model.log.LogMessage io.goobi.viewer.model.crowdsourcing.campaigns.CampaignLogMessage + io.goobi.viewer.dao.converter.StringListConverter @@ -122,6 +123,7 @@ io.goobi.viewer.dao.converter.TranslatedTextConverter io.goobi.viewer.model.log.LogMessage io.goobi.viewer.model.crowdsourcing.campaigns.CampaignLogMessage + io.goobi.viewer.dao.converter.StringListConverter + + + + + + + + + + + + + + + + + +

#{msg.archives}

+ + +
+ #{msg.error__archives_database_configuration_missing} +
+
+ +
+ #{msg.error__archives_database_not_reachable} +
+
+ +
+ #{msg.error__archives_failed_to_read_database} +
+
+ +
+ + + + +
+
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/archivesContent.xhtml b/goobi-viewer-core/src/main/resources/META-INF/resources/archivesContent.xhtml new file mode 100644 index 00000000000..e35132f7046 --- /dev/null +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/archivesContent.xhtml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + +
+ + +
+ + + + +
+ + + + + +
x
+ + +
+
+ + + + + + +
+
+ + +
+ + + + + +
+
+ +
+ #{entry.label} +
+
+
+ + + + + +
+ + +
+ +
#{msg.archives__showRecord}
+
+ +
+ +
+
+
+
+
+
+
+ + + + + + +
diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/archivesLayout.xhtml b/goobi-viewer-core/src/main/resources/META-INF/resources/archivesLayout.xhtml new file mode 100644 index 00000000000..0ab370bad16 --- /dev/null +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/archivesLayout.xhtml @@ -0,0 +1,106 @@ + + + + + + + + +
+ +
+ + +
+
+
+ +
+ +
+ + +
+ #{configurationBean.name}

#{msg.archives__archiveViewHeading}

+
+ + +
+ #{archiveBean.trueRoot.label} +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+
+
+ + +
+ + + +
+ #{msg.error__archives_database_configuration_missing} +
+
+ +
+ #{msg.error__archives_database_not_reachable} +
+
+ +
+ #{msg.error__archives_failed_to_read_database} +
+
+ + + +
+
+ + + + + + + +
+
\ No newline at end of file diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/bookmarkLists.xhtml b/goobi-viewer-core/src/main/resources/META-INF/resources/bookmarkLists.xhtml index 609a49b4602..0e171d26c2c 100644 --- a/goobi-viewer-core/src/main/resources/META-INF/resources/bookmarkLists.xhtml +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/bookmarkLists.xhtml @@ -15,7 +15,7 @@ - + diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/error.xhtml b/goobi-viewer-core/src/main/resources/META-INF/resources/error.xhtml index d6bf67ff51f..2d06cc59ef8 100644 --- a/goobi-viewer-core/src/main/resources/META-INF/resources/error.xhtml +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/error.xhtml @@ -159,6 +159,27 @@

+ + +

#{msg.errBaseXExceptionTitle}

+ + + + + +

+ #{msg.returnToPreviousPage} +
+ #{msg.returnHome} +

+
+

#{msg.errViewerConfigurationExceptionTitle}

diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/login.xhtml b/goobi-viewer-core/src/main/resources/META-INF/resources/login.xhtml index 698bcdcc971..a7e6f59e964 100644 --- a/goobi-viewer-core/src/main/resources/META-INF/resources/login.xhtml +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/login.xhtml @@ -13,7 +13,8 @@ xmlns:viewer="http://xmlns.jcp.org/jsf/composite/components/partner" xmlns:viewerComponent="http://xmlns.jcp.org/jsf/composite/components" xmlns:widgetComponent="http://xmlns.jcp.org/jsf/composite/components/widgets" - xmlns:templateComponent="http://xmlns.jcp.org/jsf/composite/themes/reference/components"> + xmlns:templateComponent="http://xmlns.jcp.org/jsf/composite/themes/reference/components" + lang="#{navigationHelper.localeString}"> @@ -69,11 +70,11 @@
- - - - - +
+ + + +
diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/pretty-standard-config.xml b/goobi-viewer-core/src/main/resources/META-INF/resources/pretty-standard-config.xml index 1c870e0ad18..9853e1c1ca0 100644 --- a/goobi-viewer-core/src/main/resources/META-INF/resources/pretty-standard-config.xml +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/pretty-standard-config.xml @@ -104,6 +104,23 @@ #{searchBean.setActiveSearchType(0)} + + + + + #{archiveBean.selectedEntryId} + + + + + + #{archiveBean.selectedEntryId} + + + + + + @@ -687,105 +704,87 @@ - #{navigationHelper.resetTheme} #{adminBean.setCurrentUser(null)} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} #{adminBean.resetCurrentUserAction} - #{navigationHelper.resetTheme} #{adminBean.setCurrentUserGroup(null)} - #{navigationHelper.resetTheme} #{adminBean.resetCurrentUserRoleAction} - #{navigationHelper.resetTheme} #{adminBean.resetCurrentUserGroupAction} - #{navigationHelper.resetTheme} + #{adminBean.resetCurrentUserRoleAction} #{adminBean.setCurrentIpRange(null)} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} #{adminBean.resetCurrentIpRangeAction} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} #{adminBean.newCurrentLicenseAction} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} - #{navigationHelper.resetTheme} @@ -1003,7 +1002,7 @@ #{crowdsourcingBean.resetTarget} - + #{navigationHelper.resetTheme} diff --git a/goobi-viewer-core/src/main/resources/META-INF/resources/resources/admin/includes/layout/admin_layout.xhtml b/goobi-viewer-core/src/main/resources/META-INF/resources/resources/admin/includes/layout/admin_layout.xhtml index a4bf282039e..a252f468744 100644 --- a/goobi-viewer-core/src/main/resources/META-INF/resources/resources/admin/includes/layout/admin_layout.xhtml +++ b/goobi-viewer-core/src/main/resources/META-INF/resources/resources/admin/includes/layout/admin_layout.xhtml @@ -22,18 +22,18 @@
-