From 9eacc082ab55aecddca66702973c4f2fca91d932 Mon Sep 17 00:00:00 2001 From: Piotr Date: Fri, 24 May 2019 09:32:22 +0200 Subject: [PATCH] Feature: Flat repository (#35) * Move from layered repo to a single layer. Add FlatRespository and its Facade * Refactor: Run tidyall -a * Fix builds on Travis by updating Text::BibTeX (#37) * Enable verbose installation * Remove traces of `Dist::Zilla` support * Use Text::BibTeX version 0.88 * Remove has 'dateTimeFormat' from BibSpaceDTO * Name routes for Teams. Remove Arch generator * Remove non-existing route for teams. Change URL for /unrelated_papers * Fix Dockerhub builds (#39) * Add wait-for script * Fix: Store hashed password after password reset. Fixes #40 * Fix bug for deleting teams. Add tests for uncovered code. Update changelog * Remove unused code. Refactor + add tests for a disabled registration * Fix attachment_url test * Replace eval LocalBibStyle.pm in _function_arith --- .editorconfig | 16 + CHANGELOG.md | 39 +- Dockerfile | 2 +- cpanfile | 4 +- fixture/bibspace_fixture.dat | Bin 943341 -> 0 bytes fixture/bibspace_fixture.json | 1288 +++++++++++++++++ lib/BibSpace.pm | 332 ++--- lib/BibSpace/Backend/IBibSpaceBackend.pm | 20 - lib/BibSpace/Backend/SmartArray.pm | 180 --- lib/BibSpace/Backend/SmartBackendHelper.pm | 79 - lib/BibSpace/Backend/SmartHash.pm | 173 --- lib/BibSpace/Controller/Authors.pm | 253 +--- lib/BibSpace/Controller/Backup.pm | 54 +- lib/BibSpace/Controller/Cron.pm | 44 +- lib/BibSpace/Controller/Helpers.pm | 28 +- lib/BibSpace/Controller/Login.pm | 91 +- lib/BibSpace/Controller/Persistence.pm | 170 +-- lib/BibSpace/Controller/Preferences.pm | 1 - lib/BibSpace/Controller/Publications.pm | 54 +- .../Controller/PublicationsExperimental.pm | 1 - lib/BibSpace/Controller/Tags.pm | 26 +- lib/BibSpace/Controller/Teams.pm | 2 +- lib/BibSpace/Controller/Types.pm | 21 +- lib/BibSpace/Converter/BibStyleConverter.pm | 8 +- lib/BibSpace/DAO/DAOFactory.pm | 2 - lib/BibSpace/DAO/Interface/IDAO.pm | 1 - lib/BibSpace/DAO/MySQL/AuthorMySQLDAO.pm | 101 +- lib/BibSpace/DAO/MySQL/AuthorshipMySQLDAO.pm | 34 +- lib/BibSpace/DAO/MySQL/EntryMySQLDAO.pm | 69 +- lib/BibSpace/DAO/MySQL/ExceptionMySQLDAO.pm | 40 +- lib/BibSpace/DAO/MySQL/LabelingMySQLDAO.pm | 37 +- lib/BibSpace/DAO/MySQL/MembershipMySQLDAO.pm | 40 +- lib/BibSpace/DAO/MySQL/TagMySQLDAO.pm | 38 +- lib/BibSpace/DAO/MySQL/TagTypeMySQLDAO.pm | 42 +- lib/BibSpace/DAO/MySQL/TeamMySQLDAO.pm | 50 +- lib/BibSpace/DAO/MySQL/TypeMySQLDAO.pm | 43 +- lib/BibSpace/DAO/MySQL/UserMySQLDAO.pm | 121 +- lib/BibSpace/DAO/MySQLDAOFactory.pm | 22 - lib/BibSpace/DAO/Redis/AuthorRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/AuthorshipRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/EntryRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/ExceptionRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/LabelingRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/MembershipRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/TagRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/TagTypeRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/TeamRedisDAO.pm | 185 --- lib/BibSpace/DAO/Redis/TypeRedisDAO.pm | 158 -- lib/BibSpace/DAO/Redis/UserRedisDAO.pm | 185 --- lib/BibSpace/DAO/RedisDAOFactory.pm | 148 -- .../DAO/SmartArray/AuthorSmartArrayDAO.pm | 140 -- .../DAO/SmartArray/AuthorshipSmartArrayDAO.pm | 141 -- .../DAO/SmartArray/EntrySmartArrayDAO.pm | 142 -- .../DAO/SmartArray/ExceptionSmartArrayDAO.pm | 142 -- .../DAO/SmartArray/LabelingSmartArrayDAO.pm | 146 -- .../DAO/SmartArray/MembershipSmartArrayDAO.pm | 139 -- .../DAO/SmartArray/TagSmartArrayDAO.pm | 141 -- .../DAO/SmartArray/TagTypeSmartArrayDAO.pm | 141 -- .../DAO/SmartArray/TeamSmartArrayDAO.pm | 139 -- .../DAO/SmartArray/TypeSmartArrayDAO.pm | 139 -- .../DAO/SmartArray/UserSmartArrayDAO.pm | 142 -- lib/BibSpace/DAO/SmartArrayDAOFactory.pm | 149 -- lib/BibSpace/Functions/BackupFunctions.pm | 68 +- lib/BibSpace/Functions/Core.pm | 14 +- lib/BibSpace/Functions/FDB.pm | 89 +- lib/BibSpace/Functions/FPublications.pm | 38 +- .../Functions/MySqlBackupFunctions.pm | 63 - lib/BibSpace/Model/Author.pm | 198 +-- lib/BibSpace/Model/Authorship.pm | 72 +- lib/BibSpace/Model/Backup.pm | 10 +- lib/BibSpace/Model/Entry.pm | 175 ++- lib/BibSpace/Model/Exception.pm | 44 +- lib/BibSpace/Model/IAuthored.pm | 40 +- lib/BibSpace/Model/IEntity.pm | 49 +- lib/BibSpace/Model/IHavingException.pm | 38 +- lib/BibSpace/Model/ILabeled.pm | 47 +- lib/BibSpace/Model/IMembered.pm | 44 +- lib/BibSpace/Model/IRelation.pm | 22 +- lib/BibSpace/Model/Labeling.pm | 68 +- lib/BibSpace/Model/Membership.pm | 60 +- .../AuthorSerializableBase.pm | 16 +- .../Model/SerializableBase/BibSpaceDTO.pm | 48 +- .../SerializableBase/UserSerializableBase.pm | 13 +- lib/BibSpace/Model/Tag.pm | 23 +- lib/BibSpace/Model/TagType.pm | 10 - lib/BibSpace/Model/Team.pm | 47 +- lib/BibSpace/Model/Type.pm | 41 +- lib/BibSpace/Model/User.pm | 39 +- lib/BibSpace/Repository/FlatRepository.pm | 156 ++ ...itoryFacade.pm => FlatRepositoryFacade.pm} | 50 +- lib/BibSpace/Repository/LayeredRepository.pm | 429 ------ lib/BibSpace/Repository/RepositoryLayer.pm | 119 +- lib/BibSpace/TestManager.pm | 6 +- lib/BibSpace/Util/DummyUidProvider.pm | 17 - lib/BibSpace/Util/EntityFactory.pm | 63 +- lib/BibSpace/Util/IntegerUidProvider.pm | 83 -- lib/BibSpace/Util/SmartUidProvider.pm | 131 -- lib/BibSpace/files/config/default.conf | 8 +- .../files/templates/authors/authors.html.ep | 55 +- .../templates/authors/edit_author.html.ep | 128 +- .../files/templates/backup/backup.html.ep | 55 +- .../templates/display/danger_zone.html.ep | 32 +- .../files/templates/login/noregister.html.ep | 7 +- lib/BibSpace/files/templates/navi.html.ep | 56 +- .../publications/manage_exceptions.html.ep | 16 +- .../publications/manage_tags.html.ep | 26 +- .../publications/show_authors.html.ep | 14 +- .../templates/tags/authors_having_tag.html.ep | 6 +- .../tags/authors_having_tag_read.html.ep | 4 +- .../files/templates/tagtypes/edit.html.ep | 8 +- .../files/templates/tagtypes/tagtypes.html.ep | 22 +- .../files/templates/teams/add_team.html.ep | 2 +- .../files/templates/teams/members.html.ep | 34 +- .../files/templates/teams/teams.html.ep | 15 +- .../files/templates/types/add.html.ep | 2 +- .../templates/types/manage_types.html.ep | 15 +- .../files/templates/types/types.html.ep | 5 +- lib/BibStyle/LocalBibStyle.pm | 23 +- t/000_prepare.t | 11 +- t/100-unit/Author.t | 108 +- t/100-unit/Authorship.t | 58 + t/100-unit/BibSpaceDTO-Date-Formatting.t | 8 +- t/100-unit/BibSpaceDTO.t | 160 +- t/100-unit/Entry-OurType-Mapping.t | 7 +- t/100-unit/Entry.t | 30 +- t/100-unit/Exception.t | 38 +- t/100-unit/Labeling.t | 25 +- t/100-unit/Membership.t | 66 + t/100-unit/RepositoryFacade.t | 302 ++-- t/100-unit/SmartArray.t | 62 - t/100-unit/SmartHash.t | 61 - t/100-unit/Tag.t | 41 +- t/100-unit/TagType.t | 18 +- t/100-unit/Team.t | 31 +- t/100-unit/Type.t | 6 +- t/100-unit/User.t | 7 + t/200-controller/Author.t | 85 ++ t/200-controller/Backup.t | 4 +- t/200-controller/Login.t | 194 ++- t/200-controller/Persistence.t | 21 +- t/200-controller/Publications.t | 28 +- t/200-controller/TagTypes.t | 104 ++ t/200-controller/Tags.t | 13 - t/200-controller/Teams.t | 134 ++ t/300-functions/BackupFunctions.t | 6 +- t/900-frontend/002_basic_gets.t | 17 +- t/900-frontend/003_login.t | 1 + t/900-frontend/004_landing_publications.t | 7 + t/900-frontend/004_publications_single.t | 33 +- t/900-frontend/0051_authors_merge.t | 101 ++ t/900-frontend/005_authors.t | 308 ++++ t/900-frontend/006_types.t | 164 +++ t/900-frontend/007_teams.t | 49 + t/900-frontend/007_user_management_anyone.t | 4 +- t/900-frontend/008_user_management_logged.t | 2 +- t/900-frontend/009_cron_and_rest.t | 70 +- t/900-frontend/009_preferences.t | 32 + t/99_rest.t | 10 +- util/ArchitectureGeneratorRepositoryTest.pl | 179 --- 159 files changed, 4784 insertions(+), 8167 deletions(-) create mode 100644 .editorconfig delete mode 100644 fixture/bibspace_fixture.dat create mode 100644 fixture/bibspace_fixture.json delete mode 100644 lib/BibSpace/Backend/IBibSpaceBackend.pm delete mode 100644 lib/BibSpace/Backend/SmartArray.pm delete mode 100644 lib/BibSpace/Backend/SmartBackendHelper.pm delete mode 100644 lib/BibSpace/Backend/SmartHash.pm delete mode 100644 lib/BibSpace/DAO/Redis/AuthorRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/AuthorshipRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/EntryRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/ExceptionRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/LabelingRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/MembershipRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/TagRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/TagTypeRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/TeamRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/TypeRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/Redis/UserRedisDAO.pm delete mode 100644 lib/BibSpace/DAO/RedisDAOFactory.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/AuthorSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/AuthorshipSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/EntrySmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/ExceptionSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/LabelingSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/MembershipSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/TagSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/TagTypeSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/TeamSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/TypeSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArray/UserSmartArrayDAO.pm delete mode 100644 lib/BibSpace/DAO/SmartArrayDAOFactory.pm create mode 100644 lib/BibSpace/Repository/FlatRepository.pm rename lib/BibSpace/Repository/{RepositoryFacade.pm => FlatRepositoryFacade.pm} (87%) delete mode 100644 lib/BibSpace/Repository/LayeredRepository.pm delete mode 100644 lib/BibSpace/Util/DummyUidProvider.pm delete mode 100644 lib/BibSpace/Util/IntegerUidProvider.pm delete mode 100644 lib/BibSpace/Util/SmartUidProvider.pm create mode 100644 t/100-unit/Authorship.t create mode 100644 t/100-unit/Membership.t delete mode 100644 t/100-unit/SmartArray.t delete mode 100644 t/100-unit/SmartHash.t create mode 100644 t/200-controller/Author.t create mode 100644 t/200-controller/TagTypes.t create mode 100644 t/200-controller/Teams.t create mode 100644 t/900-frontend/0051_authors_merge.t create mode 100644 t/900-frontend/005_authors.t create mode 100644 t/900-frontend/006_types.t create mode 100644 t/900-frontend/007_teams.t create mode 100644 t/900-frontend/009_preferences.t delete mode 100644 util/ArchitectureGeneratorRepositoryTest.pl diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..fe39f35 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,16 @@ +# top-most EditorConfig file +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true +indent_style = space +indent_size = 2 + + + + + + diff --git a/CHANGELOG.md b/CHANGELOG.md index 0204465..3cbf699 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,20 @@ ### Changelog ### +#### v0.6.0 - 24.05.2019 + + * Fixed: + * (Major) Issue with some changes not being persisted in the database (#21). + * Changed: + * Default backup format to JSON + * Removed: + * Support for `Storable` backup + * (Internal) Support for build-in in-memory cache `SmartLayer`. + #### v0.5.4 - 04.05.2019 +This version supports backups in format: `Storable` and `JSON`. +Use it to migrate your data from `0.5.x` to `0.6.x`. + * Added: * Support for Mojolicious `>7.55` by changing route placeholders from `()` to `<>` (#38) * Official Docker image with Dockerhub build (#39) @@ -12,17 +25,19 @@ #### v0.5.3 - 07.2018 -This version is probably the only one that supports both types of backups: Storable and JSON. -Use it to migrate your data from 0.5.x to 0.6.x. - -* Deprecation: Storable backups are marked as deprecated now. Use JSON backup instead. -* Feature: Add support for independent JSON backup and restore +This version supports backups in format: `Storable` and `JSON`. +Use it to migrate your data from `0.5.x` to `0.6.x`. -* Fix: Non-ANSI usernames are now properly encoded with UTF-8 on the Publications page -* Fix: External link to DateTime formats uses now permalink to CPAN -* Fix: Show recenlty added/modified works correctly for less than 10 objects in system -* Fix: Harden tests for less than 10 objects in system -* Internal: Add SerializableBase to BibSpace Entities +* Added: + * Support for independent JSON backup and restore + * (Internal) Add SerializableBase to BibSpace Entities +* Fixed: + * Non-ANSI usernames are now properly encoded with UTF-8 on the Publications page + * External link to DateTime formats uses now permalink to CPAN + * Show recently added/modified works correctly for less than 10 objects in system + * Tests for less than 10 objects in system +* Deprecated: + * Storable backups. Use JSON backup instead. #### v0.5.2 - 08.2017 #### @@ -71,7 +86,7 @@ Use it to migrate your data from 0.5.x to 0.6.x. #### v0.4.5 - 08.10.2016 #### * Code refactoring - towards OO design and getting rid of core.pm - edit_author, authors, tags -* Various bugfixes, e.g., showing publications with no tag for autor no longer returns 404. +* Various bugfixes, e.g., showing publications with no tag for author no longer returns 404. #### v0.4.4 - 18.09.2016 #### @@ -85,7 +100,7 @@ Use it to migrate your data from 0.5.x to 0.6.x. #### v0.4.1 - 31.05.2016 #### -* Add function to change download urls from direct file paths to file serving function +* Add function to change download URLs from direct file paths to file serving function * Add function to remove attachments * Fixing multiple minor bugs diff --git a/Dockerfile b/Dockerfile index 20c04d4..e8fef94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -44,7 +44,7 @@ RUN apk update \ && rm -rf /var/cache/apk/* RUN echo "Europe/Berlin" > /config/etc/timezone -LABEL version="0.5.4" +LABEL version="0.6.0" EXPOSE 8083 HEALTHCHECK --interval=30s --timeout=15s CMD curl --fail http://localhost:8083/system_status || exit 1 diff --git a/cpanfile b/cpanfile index 86bb5ac..86cc1cc 100644 --- a/cpanfile +++ b/cpanfile @@ -20,14 +20,16 @@ requires 'Module::CPANfile' , '>= 0.0'; requires 'Mojolicious' , '>= 8.0'; requires 'Mojolicious::Plugin::RenderFile' , '>= 0.0'; requires 'Moose' , '>= 2.0604'; +requires 'MooseX::Role::Parameterized' , '>= 1.00'; # Required for Moose 2.2011 +requires 'MooseX::Types' , '>= 0.19'; # Required for Moose 2.2011 requires 'MooseX::ClassAttribute' , '>= 0.0'; +requires 'MooseX::Privacy' , '>=0.0'; requires 'MooseX::Singleton' , '>= 0.0'; requires 'MooseX::Storage' , '>= 0.0'; requires 'MooseX::StrictConstructor' , '>= 0.0'; requires 'Path::Tiny' , '>= 0.0'; requires 'Scalar::Util' , '>= 0.0'; requires 'Session::Token' , '>= 0.0'; -requires 'Storable' , '>= 0.0'; requires 'TeX::Encode' , '>= 0.0'; requires 'Text::ASCIIMathML' , '>= 0.0'; requires 'Text::BibTeX' , '== 0.88'; # 0.86 and 0.87 are broken - use 0.85 or 0.88 diff --git a/fixture/bibspace_fixture.dat b/fixture/bibspace_fixture.dat deleted file mode 100644 index ee65549e42f2474eef39e18c3ef057db7b33217a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 943341 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 z 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 diff --git a/fixture/bibspace_fixture.json b/fixture/bibspace_fixture.json new file mode 100644 index 0000000..7b6021e --- /dev/null +++ b/fixture/bibspace_fixture.json @@ -0,0 +1,1288 @@ +{ + "data" : { + "User" : [ + { + "old_mysql_id" : 2, + "pass2" : "gabQ/Wh1O4X/rxFyw1MN9.", + "real_name" : "James Bond", + "rank" : 1, + "tennant_id" : 0, + "login" : "j.bond007", + "email" : "test@example.com", + "pass" : "$2a$08$gabQ/Wh1O4X/rxFyw1MN9.JAW7fpGkgnqL4n1MBn0Fke3trEwIFEK", + "master_id" : 0, + "registration_time" : "2017-02-08T18:20:18", + "id" : 2, + "pass3" : null, + "last_login" : "2017-02-08T18:20:27" + }, + { + "real_name" : "Admin", + "old_mysql_id" : 1, + "pass2" : "RjsJK5.hcmqMphocOzVsUO", + "login" : "pub_admin", + "tennant_id" : 0, + "rank" : 2, + "id" : 1, + "registration_time" : "2017-02-08T14:21:41", + "master_id" : 0, + "email" : "pub_admin@example.com", + "pass" : "$2a$08$RjsJK5.hcmqMphocOzVsUOAbdhZBdl9gNlGDzl69AyApzIQO2P7TK", + "last_login" : "2017-12-10T19:32:04", + "pass3" : null + } + ], + "Tag" : [ + { + "name" : "Networking", + "permalink" : "N", + "id" : 4, + "old_mysql_id" : 4, + "type" : 1 + }, + { + "type" : 1, + "id" : 6, + "old_mysql_id" : 6, + "permalink" : null, + "name" : "Virtualization" + }, + { + "name" : "Simulation", + "permalink" : null, + "old_mysql_id" : 8, + "id" : 8, + "type" : 1 + }, + { + "id" : 9, + "old_mysql_id" : 9, + "type" : 1, + "name" : "Meta-models", + "permalink" : null + }, + { + "id" : 10, + "old_mysql_id" : 10, + "type" : 1, + "name" : "Performance", + "permalink" : null + }, + { + "type" : 1, + "old_mysql_id" : 11, + "id" : 11, + "permalink" : null, + "name" : "Prediction" + }, + { + "name" : "QPME_Bibliography", + "permalink" : null, + "old_mysql_id" : 15, + "id" : 15, + "type" : 3 + }, + { + "permalink" : null, + "name" : "Cloud", + "type" : 1, + "id" : 19, + "old_mysql_id" : 19 + }, + { + "type" : 1, + "old_mysql_id" : 20, + "id" : 20, + "permalink" : null, + "name" : "QPN" + }, + { + "name" : "BUNGEE", + "permalink" : null, + "old_mysql_id" : 21, + "id" : 21, + "type" : 1 + }, + { + "name" : "DML", + "permalink" : null, + "id" : 22, + "old_mysql_id" : 22, + "type" : 1 + }, + { + "name" : "DQL", + "permalink" : null, + "id" : 23, + "old_mysql_id" : 23, + "type" : 1 + }, + { + "old_mysql_id" : 28, + "id" : 28, + "type" : 1, + "name" : "SDN", + "permalink" : null + }, + { + "id" : 30, + "old_mysql_id" : 30, + "type" : 1, + "name" : "SPEC", + "permalink" : null + }, + { + "old_mysql_id" : 31, + "id" : 31, + "type" : 1, + "name" : "Award", + "permalink" : null + }, + { + "id" : 33, + "old_mysql_id" : 33, + "type" : 1, + "name" : "Elasticity", + "permalink" : null + }, + { + "old_mysql_id" : 36, + "id" : 36, + "type" : 1, + "name" : "Isolation", + "permalink" : null + }, + { + "old_mysql_id" : 41, + "id" : 41, + "type" : 1, + "name" : "Power", + "permalink" : null + }, + { + "id" : 43, + "old_mysql_id" : 43, + "type" : 1, + "name" : "Reliability", + "permalink" : null + }, + { + "name" : "Security", + "permalink" : null, + "old_mysql_id" : 44, + "id" : 44, + "type" : 1 + }, + { + "permalink" : null, + "name" : "Tool", + "type" : 1, + "old_mysql_id" : 49, + "id" : 49 + }, + { + "type" : 1, + "id" : 51, + "old_mysql_id" : 51, + "permalink" : null, + "name" : "QPME" + }, + { + "permalink" : null, + "name" : "Optimization", + "type" : 1, + "id" : 52, + "old_mysql_id" : 52 + }, + { + "old_mysql_id" : 53, + "id" : 53, + "type" : 2, + "name" : "Design_of_enterprise_system_architectures", + "permalink" : "A1" + }, + { + "name" : "Formal_architecture_modeling", + "permalink" : "A2", + "id" : 54, + "old_mysql_id" : 54, + "type" : 2 + }, + { + "type" : 2, + "id" : 55, + "old_mysql_id" : 55, + "permalink" : "A3", + "name" : "Analytical_and_simulation-based_analysis" + }, + { + "id" : 56, + "old_mysql_id" : 56, + "type" : 2, + "name" : "Model_extraction,_calibration_and_maintenance", + "permalink" : "A4" + }, + { + "id" : 57, + "old_mysql_id" : 57, + "type" : 2, + "name" : "Metrics_and_benchmarking_methodologies", + "permalink" : "B1" + }, + { + "type" : 2, + "id" : 58, + "old_mysql_id" : 58, + "permalink" : "B2", + "name" : "Instrumentation_profiling_and_workload_characterization" + }, + { + "permalink" : "B3", + "name" : "Statistical_estimation_and_machine_learning", + "type" : 2, + "id" : 59, + "old_mysql_id" : 59 + }, + { + "name" : "Online_monitoring_and_forecasting", + "permalink" : "B4", + "old_mysql_id" : 60, + "id" : 60, + "type" : 2 + }, + { + "permalink" : "C1", + "name" : "Data_center_resource_management", + "type" : 2, + "old_mysql_id" : 61, + "id" : 61 + }, + { + "type" : 2, + "id" : 62, + "old_mysql_id" : 62, + "permalink" : "C2", + "name" : "Application_quality_of_service_management" + }, + { + "type" : 2, + "id" : 63, + "old_mysql_id" : 63, + "permalink" : "C3", + "name" : "Power-energy_efficient_computing" + }, + { + "type" : 2, + "old_mysql_id" : 64, + "id" : 64, + "permalink" : "C4", + "name" : "Multi-criteria_optimization" + }, + { + "type" : 1, + "id" : 65, + "old_mysql_id" : 65, + "permalink" : null, + "name" : "Self-adaptive-systems" + }, + { + "permalink" : null, + "name" : "Self-aware-computing", + "type" : 1, + "old_mysql_id" : 66, + "id" : 66 + }, + { + "permalink" : "DNI", + "name" : "DNI", + "type" : 3, + "old_mysql_id" : 67, + "id" : 67 + }, + { + "name" : "HInjector", + "permalink" : null, + "old_mysql_id" : 68, + "id" : 68, + "type" : 1 + }, + { + "id" : 69, + "old_mysql_id" : 69, + "type" : 3, + "name" : "Supervised_by_Nikolas_Herbst", + "permalink" : null + }, + { + "old_mysql_id" : 70, + "id" : 70, + "type" : 3, + "name" : "Thesis_supervised_by_SE_member", + "permalink" : null + }, + { + "name" : "Thesis_supervised_by_Simon_Spinner", + "permalink" : null, + "old_mysql_id" : 71, + "id" : 71, + "type" : 3 + }, + { + "name" : "Talks", + "permalink" : "talks", + "old_mysql_id" : 72, + "id" : 72, + "type" : 3 + }, + { + "id" : 73, + "old_mysql_id" : 73, + "type" : 3, + "name" : "Thesis_supervised_by_Nikolaus_Huber", + "permalink" : null + }, + { + "permalink" : null, + "name" : "Thesis_supervised_by_Fabian_Brosig", + "type" : 3, + "id" : 74, + "old_mysql_id" : 74 + }, + { + "permalink" : null, + "name" : "Thesis_supervised_by_Juergen_Walter", + "type" : 3, + "old_mysql_id" : 75, + "id" : 75 + }, + { + "permalink" : null, + "name" : "Thesis_supervised_by_Joakim_vonKistowski", + "type" : 3, + "id" : 76, + "old_mysql_id" : 76 + }, + { + "permalink" : null, + "name" : "Supervised_PhD_Thesis", + "type" : 3, + "old_mysql_id" : 77, + "id" : 77 + }, + { + "id" : 78, + "old_mysql_id" : 78, + "type" : 3, + "name" : "Interpolation", + "permalink" : null + }, + { + "type" : 3, + "id" : 81, + "old_mysql_id" : 81, + "permalink" : null, + "name" : "Thesis_supervised_by_Aleksandar_Milenkoski" + }, + { + "name" : "Media-coverage", + "permalink" : null, + "old_mysql_id" : 230, + "id" : 230, + "type" : 3 + }, + { + "type" : 3, + "id" : 231, + "old_mysql_id" : 231, + "permalink" : null, + "name" : "Thesis_supervised_by_Piotr_Rygielski" + }, + { + "permalink" : null, + "name" : "Thesis_supervised_by_Lukas_Ifflaender", + "type" : 3, + "id" : 240, + "old_mysql_id" : 240 + } + ], + "Labeling" : [ + { + "tag_id" : 4, + "entry_id" : 675 + }, + { + "entry_id" : 202, + "tag_id" : 6 + }, + { + "tag_id" : 6, + "entry_id" : 435 + }, + { + "entry_id" : 1105, + "tag_id" : 6 + }, + { + "tag_id" : 8, + "entry_id" : 1077 + }, + { + "entry_id" : 435, + "tag_id" : 10 + }, + { + "entry_id" : 1077, + "tag_id" : 10 + }, + { + "entry_id" : 1099, + "tag_id" : 10 + }, + { + "tag_id" : 11, + "entry_id" : 1077 + }, + { + "tag_id" : 11, + "entry_id" : 1111 + }, + { + "entry_id" : 202, + "tag_id" : 19 + }, + { + "entry_id" : 435, + "tag_id" : 19 + }, + { + "tag_id" : 19, + "entry_id" : 1105 + }, + { + "tag_id" : 30, + "entry_id" : 588 + }, + { + "entry_id" : 781, + "tag_id" : 30 + }, + { + "tag_id" : 33, + "entry_id" : 202 + }, + { + "entry_id" : 435, + "tag_id" : 43 + }, + { + "entry_id" : 435, + "tag_id" : 44 + }, + { + "tag_id" : 49, + "entry_id" : 435 + }, + { + "entry_id" : 1111, + "tag_id" : 52 + }, + { + "tag_id" : 52, + "entry_id" : 1159 + }, + { + "entry_id" : 1105, + "tag_id" : 53 + }, + { + "tag_id" : 54, + "entry_id" : 1077 + }, + { + "entry_id" : 1077, + "tag_id" : 55 + }, + { + "entry_id" : 202, + "tag_id" : 57 + }, + { + "tag_id" : 57, + "entry_id" : 435 + }, + { + "tag_id" : 58, + "entry_id" : 435 + }, + { + "entry_id" : 1159, + "tag_id" : 58 + }, + { + "entry_id" : 1159, + "tag_id" : 59 + }, + { + "tag_id" : 60, + "entry_id" : 1111 + }, + { + "entry_id" : 1099, + "tag_id" : 61 + }, + { + "entry_id" : 1111, + "tag_id" : 61 + }, + { + "tag_id" : 61, + "entry_id" : 1159 + }, + { + "entry_id" : 1159, + "tag_id" : 64 + }, + { + "entry_id" : 1159, + "tag_id" : 65 + }, + { + "tag_id" : 66, + "entry_id" : 1111 + }, + { + "entry_id" : 435, + "tag_id" : 68 + }, + { + "tag_id" : 69, + "entry_id" : 1159 + }, + { + "tag_id" : 70, + "entry_id" : 1159 + }, + { + "tag_id" : 71, + "entry_id" : 1159 + }, + { + "tag_id" : 77, + "entry_id" : 1166 + } + ], + "Author" : [ + { + "uid" : "RygielskiPiotr", + "name" : "RygielskiPiotr", + "old_mysql_id" : 527, + "display" : 1, + "id" : 527, + "master_id" : 527 + }, + { + "master_id" : 591, + "old_mysql_id" : 591, + "id" : 591, + "display" : 1, + "uid" : "SPEC", + "name" : "SPEC" + }, + { + "name" : "HerbstNikolas", + "uid" : "HerbstNikolas", + "display" : 1, + "id" : 1121, + "old_mysql_id" : 1121, + "master_id" : 1121 + }, + { + "name" : "MilenkoskiAleksandar", + "uid" : "MilenkoskiAleksandar", + "id" : 1389, + "display" : 1, + "old_mysql_id" : 1389, + "master_id" : 1389 + }, + { + "uid" : "GrohmannJohannes", + "name" : "GrohmannJohannes", + "master_id" : 1486, + "old_mysql_id" : 1486, + "display" : 1, + "id" : 1486 + }, + { + "name" : "ExampleJohny", + "uid" : "ExampleJohny", + "display" : 1, + "id" : 1487, + "old_mysql_id" : 1487, + "master_id" : 1487 + }, + { + "master_id" : 1121, + "id" : 1488, + "display" : 0, + "old_mysql_id" : 1488, + "name" : "HerbstNikolasRoman", + "uid" : "HerbstNikolasRoman" + }, + { + "old_mysql_id" : 1489, + "id" : 1489, + "display" : 0, + "master_id" : 1489, + "uid" : "InvisibleHenry", + "name" : "InvisibleHenry" + }, + { + "master_id" : 1490, + "display" : 0, + "id" : 1490, + "old_mysql_id" : 1490, + "name" : "TestMaster", + "uid" : "TestMaster" + }, + { + "display" : 0, + "id" : 1491, + "old_mysql_id" : 1491, + "master_id" : 1491, + "name" : "TestMinion", + "uid" : "TestMinion" + } + ], + "Type" : [ + { + "id" : null, + "onLanding" : 0, + "old_mysql_id" : null, + "our_type" : "incollection", + "description" : null, + "bibtexTypes" : [ + "incollection" + ] + }, + { + "onLanding" : 1, + "id" : null, + "old_mysql_id" : null, + "our_type" : "techreport", + "description" : null, + "bibtexTypes" : [ + "techreport" + ] + }, + { + "bibtexTypes" : [ + "mastersthesis" + ], + "description" : null, + "id" : null, + "onLanding" : 0, + "old_mysql_id" : null, + "our_type" : "mastersthesis" + }, + { + "bibtexTypes" : [ + "inbook" + ], + "description" : null, + "onLanding" : 0, + "id" : null, + "old_mysql_id" : null, + "our_type" : "inbook" + }, + { + "onLanding" : 1, + "id" : null, + "old_mysql_id" : null, + "our_type" : "inproceedings", + "description" : null, + "bibtexTypes" : [ + "incollection", + "inproceedings" + ] + }, + { + "old_mysql_id" : null, + "our_type" : "unpublished", + "id" : null, + "onLanding" : 0, + "bibtexTypes" : [ + "unpublished" + ], + "description" : null + }, + { + "id" : null, + "onLanding" : 1, + "our_type" : "manual", + "old_mysql_id" : null, + "bibtexTypes" : [ + "manual" + ], + "description" : null + }, + { + "old_mysql_id" : null, + "our_type" : "theses", + "onLanding" : 1, + "id" : null, + "bibtexTypes" : [ + "mastersthesis", + "phdthesis" + ], + "description" : null + }, + { + "bibtexTypes" : [ + "incollection" + ], + "description" : null, + "old_mysql_id" : null, + "our_type" : "bibtex-incollection", + "id" : null, + "onLanding" : 0 + }, + { + "description" : null, + "bibtexTypes" : [ + "book", + "inbook", + "proceedings" + ], + "our_type" : "book", + "old_mysql_id" : null, + "onLanding" : 0, + "id" : null + }, + { + "old_mysql_id" : null, + "our_type" : "article", + "onLanding" : 1, + "id" : null, + "bibtexTypes" : [ + "article" + ], + "description" : null + }, + { + "description" : null, + "bibtexTypes" : [ + "misc" + ], + "onLanding" : 1, + "id" : null, + "our_type" : "misc", + "old_mysql_id" : null + }, + { + "bibtexTypes" : [ + "phdthesis" + ], + "description" : null, + "id" : null, + "onLanding" : 0, + "old_mysql_id" : null, + "our_type" : "phdthesis" + }, + { + "bibtexTypes" : [ + "mastersthesis", + "phdthesis" + ], + "description" : null, + "onLanding" : 0, + "id" : null, + "our_type" : "supervised_theses", + "old_mysql_id" : null + }, + { + "old_mysql_id" : null, + "our_type" : "proceedings", + "onLanding" : 0, + "id" : null, + "bibtexTypes" : [ + "proceedings" + ], + "description" : null + }, + { + "description" : null, + "bibtexTypes" : [ + "inproceedings" + ], + "id" : null, + "onLanding" : 0, + "old_mysql_id" : null, + "our_type" : "bibtex-inproceedings" + }, + { + "bibtexTypes" : [ + "book", + "phdthesis", + "proceedings" + ], + "description" : null, + "our_type" : "volumes", + "old_mysql_id" : null, + "onLanding" : 0, + "id" : null + } + ], + "Membership" : [ + { + "stop" : 0, + "author_id" : 527, + "start" : 0, + "team_id" : 1 + }, + { + "team_id" : 3, + "start" : 2009, + "author_id" : 527, + "stop" : 2012 + }, + { + "stop" : 0, + "author_id" : 1486, + "start" : 0, + "team_id" : 1 + } + ], + "Entry" : [ + { + "creation_time" : "2017-02-08T17:53:52", + "abstract" : "{Elasticity is the ability of a software system to dynamically adapt the amount of the resources it provides to clients as their workloads increase or decrease. In the context of cloud computing, automated resizing of a virtual machine's resources can be considered as a key step towards optimisation of a system's cost and energy efficiency. Existing work on cloud computing is limited to the technical view of implementing elastic systems, and definitions of scalability have not been extended to cover elasticity. This study thesis presents a detailed discussion of elasticity, proposes metrics as well as measurement techniques, and outlines next steps for enabling comparisons between cloud computing offerings on the basis of elasticity. I discuss results of our work on measuring elasticity of thread pools provided by the Java virtual machine, as well as an experiment setup for elastic CPU time slice resizing in a virtualized environment. An experiment setup is presented as future work for dynamically adding and removing z/VM Linux virtual machine instances to a performance relevant group of virtualized servers.}", + "hidden" : 0, + "html_bib" : "
\n@mastersthesis{Herbst2011a,\n  abstract = {{Elasticity is the ability of a software system to dynamically adapt the amount of the resources it provides to clients as their workloads increase or decrease. In the context of cloud computing, automated resizing of a virtual machine's resources can be considered as a key step towards optimisation of a system's cost and energy efficiency. Existing work on cloud computing is limited to the technical view of implementing elastic systems, and definitions of scalability have not been extended to cover elasticity. This study thesis presents a detailed discussion of elasticity, proposes metrics as well as measurement techniques, and outlines next steps for enabling comparisons between cloud computing offerings on the basis of elasticity. I discuss results of our work on measuring elasticity of thread pools provided by the Java virtual machine, as well as an experiment setup for elastic CPU time slice resizing in a virtualized environment. An experiment setup is presented as future work for dynamically adding and removing z/VM Linux virtual machine instances to a performance relevant group of virtualized servers.}},\n  address = {Am Fasanengarten 5, 76131 Karlsruhe, Germany},\n  author = {Nikolas Roman Herbst},\n  keywords = {Cloud, Resource Elasticity},\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/202.pdf},\n  school = {{Karlsruhe Institute of Technology (KIT)}},\n  title = {{Quantifying the Impact of Configuration Space for Elasticity Benchmarking}},\n  type = {{Study Thesis}},\n  year = {2011}\n}\n
\n\n", + "year" : 2011, + "old_mysql_id" : 202, + "html" : "Nikolas Roman Herbst.\nQuantifying the Impact of Configuration Space for Elasticity Benchmarking.\nStudy Thesis, Karlsruhe Institute of Technology (KIT), Am Fasanengarten 5, 76131 Karlsruhe, Germany, 2011.\n[ bib | Abstract\n | pdf\n ]\n
@mastersthesis{Herbst2011a,\n  abstract = {{Elasticity is the ability of a software system to dynamically adapt the amount of the resources it provides to clients as their workloads increase or decrease. In the context of cloud computing, automated resizing of a virtual machine's resources can be considered as a key step towards optimisation of a system's cost and energy efficiency. Existing work on cloud computing is limited to the technical view of implementing elastic systems, and definitions of scalability have not been extended to cover elasticity. This study thesis presents a detailed discussion of elasticity, proposes metrics as well as measurement techniques, and outlines next steps for enabling comparisons between cloud computing offerings on the basis of elasticity. I discuss results of our work on measuring elasticity of thread pools provided by the Java virtual machine, as well as an experiment setup for elastic CPU time slice resizing in a virtualized environment. An experiment setup is presented as future work for dynamically adding and removing z/VM Linux virtual machine instances to a performance relevant group of virtualized servers.}},\n  address = {Am Fasanengarten 5, 76131 Karlsruhe, Germany},\n  author = {Nikolas Roman Herbst},\n  keywords = {Cloud, Resource Elasticity},\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/202.pdf},\n  school = {{Karlsruhe Institute of Technology (KIT)}},\n  title = {{Quantifying the Impact of Configuration Space for Elasticity Benchmarking}},\n  type = {{Study Thesis}},\n  year = {2011},\n}\n\n
\n
{Elasticity is the ability of a software system to dynamically adapt the amount of the resources it provides to clients as their workloads increase or decrease. In the context of cloud computing, automated resizing of a virtual machine's resources can be considered as a key step towards optimisation of a system's cost and energy efficiency. Existing work on cloud computing is limited to the technical view of implementing elastic systems, and definitions of scalability have not been extended to cover elasticity. This study thesis presents a detailed discussion of elasticity, proposes metrics as well as measurement techniques, and outlines next steps for enabling comparisons between cloud computing offerings on the basis of elasticity. I discuss results of our work on measuring elasticity of thread pools provided by the Java virtual machine, as well as an experiment setup for elastic CPU time slice resizing in a virtualized environment. An experiment setup is presented as future work for dynamically adding and removing z/VM Linux virtual machine instances to a performance relevant group of virtualized servers.}
", + "bib" : "@mastersthesis{Herbst2011a,\n abstract = {{Elasticity is the ability of a software system to dynamically adapt the amount of the resources it provides to clients as their workloads increase or decrease. In the context of cloud computing, automated resizing of a virtual machine's resources can be considered as a key step towards optimisation of a system's cost and energy efficiency. Existing work on cloud computing is limited to the technical view of implementing elastic systems, and definitions of scalability have not been extended to cover elasticity. This study thesis presents a detailed discussion of elasticity, proposes metrics as well as measurement techniques, and outlines next steps for enabling comparisons between cloud computing offerings on the basis of elasticity. I discuss results of our work on measuring elasticity of thread pools provided by the Java virtual machine, as well as an experiment setup for elastic CPU time slice resizing in a virtualized environment. An experiment setup is presented as future work for dynamically adding and removing z/VM Linux virtual machine instances to a performance relevant group of virtualized servers.}},\n address = {Am Fasanengarten 5, 76131 Karlsruhe, Germany},\n author = {Nikolas Roman Herbst},\n keywords = {Cloud, Resource Elasticity},\n school = {{Karlsruhe Institute of Technology (KIT)}},\n title = {{Quantifying the Impact of Configuration Space for Elasticity Benchmarking}},\n type = {{Study Thesis}},\n year = {2011},\n pdf = {http://localhost:8083/publications/download/paper/202.pdf},\n}\n\n", + "bibtex_key" : "Herbst2011a", + "id" : 202, + "entry_type" : "paper", + "_bibtex_type" : "mastersthesis", + "need_html_regen" : 0, + "title" : "{Quantifying the Impact of Configuration Space for Elasticity Benchmarking}", + "month" : 0, + "modified_time" : "2017-12-10T19:35:01" + }, + { + "html" : "Aleksandar Milenkoski, Bryan D. Payne, Nuno Antunes, Marco Vieira, and Samuel Kounev.\nHInjector: Injecting Hypercall Attacks for Evaluating VMI-based Intrusion Detection Systems (Poster Paper).\nIn The 2013 Annual Computer Security Applications Conference (ACSAC 2013), New Orleans, Louisiana, USA, March 2013. Applied Computer Security Associates (ACSA), Maryland, USA.\nMarch 2013.\n[ bib | pdf\n ]\n
@inproceedings{MiPaAnViKo2013-ACSAC-HInjector,\n  address = {Maryland, USA},\n  author = {Aleksandar Milenkoski and Bryan D. Payne and Nuno Antunes and Marco Vieira and Samuel Kounev},\n  booktitle = {The 2013 Annual Computer Security Applications Conference (ACSAC 2013)},\n  publisher = {{Applied Computer Security Associates (ACSA)}},\n  location = {New Orleans, Louisiana, USA},\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/435.pdf},\n  title = {{HInjector: Injecting Hypercall Attacks for Evaluating VMI-based Intrusion Detection Systems}},\n  titleaddon = {{(Poster Paper)}},\n  year = {2013},\n  month = {March},\n}\n\n
", + "bib" : "@inproceedings{MiPaAnViKo2013-ACSAC-HInjector,\n address = {Maryland, USA},\n author = {Aleksandar Milenkoski and Bryan D. Payne and Nuno Antunes and Marco Vieira and Samuel Kounev},\n booktitle = {The 2013 Annual Computer Security Applications Conference (ACSAC 2013)},\n publisher = {{Applied Computer Security Associates (ACSA)}},\n location = {New Orleans, Louisiana, USA},\n title = {{HInjector: Injecting Hypercall Attacks for Evaluating VMI-based Intrusion Detection Systems}},\n titleaddon = {{(Poster Paper)}},\n year = {2013},\n month = {March},\n pdf = {http://localhost:8083/publications/download/paper/435.pdf},\n}\n\n", + "bibtex_key" : "MiPaAnViKo2013-ACSAC-HInjector", + "entry_type" : "paper", + "need_html_regen" : 0, + "_bibtex_type" : "inproceedings", + "id" : 435, + "month" : 3, + "title" : "{HInjector: Injecting Hypercall Attacks for Evaluating VMI-based Intrusion Detection Systems}", + "modified_time" : "2017-12-10T19:35:01", + "creation_time" : "2017-02-08T17:53:53", + "hidden" : 0, + "abstract" : null, + "year" : 2013, + "html_bib" : "
\n@inproceedings{MiPaAnViKo2013-ACSAC-HInjector,\n  address = {Maryland, USA},\n  author = {Aleksandar Milenkoski and Bryan D. Payne and Nuno Antunes and Marco Vieira and Samuel Kounev},\n  booktitle = {The 2013 Annual Computer Security Applications Conference (ACSAC 2013)},\n  publisher = {{Applied Computer Security Associates (ACSA)}},\n  location = {New Orleans, Louisiana, USA},\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/435.pdf},\n  title = {{HInjector: Injecting Hypercall Attacks for Evaluating VMI-based Intrusion Detection Systems}},\n  titleaddon = {{(Poster Paper)}},\n  year = {2013},\n  month = {March}\n}\n
\n\n", + "old_mysql_id" : 435 + }, + { + "month" : 11, + "title" : "{SPECjAppServer2002 - Industry-standard enterprise Java application server benchmark (J2EE 1.3).}", + "modified_time" : "2017-02-08T02:00:03", + "bib" : "@misc{sp2002-SPEC-SPECjAppServer2002,\n abstract = {SPECjAppServer2002 (Java Application Server) is a client/server benchmark for measuring the performance of Java Enterprise Application Servers using a subset of J2EE APIs in a complete end-to-end web application. It is the same as SPECjAppServer2001 (released in September 2002) except that the Enterprise Java Beans (EJBs) are defined using the EJB 2.0 specification instead of the EJB 1.1 specification. SPECjAppServer2002 can therefore take advantage of several EJB 2.0 features such as local interfaces, the EJB-QL query language, and Container Managed Relationships (CMR) between entity beans. Joining the client-side SPECjvm98 and the server side SPECjbb2000, SPECjAppServer2002 and SPECjAppServer2001 continue the SPEC tradition of giving Java users the most objective and representative benchmarks for measuring a system's ability to run Java applications. SPEC has designed the SPECjAppServer2002 benchmark to exercise the Java Enterprise Application Server, the Java Virtual Machine (JVM), as well as the server Systems Under Test (SUT). As a true J2EE application the benchmark will require a functional RDBMS (for JDBC) and a Web Server, but the benchmark has been designed so that the SUT can be a single system. Please note that while the SPECjAppServer2002 suite is still available for purchase, the suite has been retired, no further results are being accepted for publication, and support is no longer provided.},\n author = {SPEC},\n howpublished = {Standard Performance Evaluation Corporation},\n month = {November},\n title = {{SPECjAppServer2002 - Industry-standard enterprise Java application server benchmark (J2EE 1.3).}},\n url = {http://www.spec.org/jAppServer2002/},\n year = {2002},\n}\n\n", + "bibtex_key" : "sp2002-SPEC-SPECjAppServer2002", + "html" : "SPEC.\nSPECjAppServer2002 - Industry-standard enterprise Java application server benchmark (J2EE 1.3).\nStandard Performance Evaluation Corporation, November 2002.\n[ bib | abstract\n | http\n ]\n
@misc{sp2002-SPEC-SPECjAppServer2002,\n  author = {SPEC},\n  howpublished = {Standard Performance Evaluation Corporation},\n  month = {November},\n  title = {{SPECjAppServer2002 - Industry-standard enterprise Java application server benchmark (J2EE 1.3).}},\n  url = {http://www.spec.org/jAppServer2002/},\n  year = {2002},\n}\n\n
\n
SPECjAppServer2002 (Java Application Server) is a client/server benchmark for measuring the performance of Java Enterprise Application Servers using a subset of J2EE APIs in a complete end-to-end web application. It is the same as SPECjAppServer2001 (released in September 2002) except that the Enterprise Java Beans (EJBs) are defined using the EJB 2.0 specification instead of the EJB 1.1 specification. SPECjAppServer2002 can therefore take advantage of several EJB 2.0 features such as local interfaces, the EJB-QL query language, and Container Managed Relationships (CMR) between entity beans. Joining the client-side SPECjvm98 and the server side SPECjbb2000, SPECjAppServer2002 and SPECjAppServer2001 continue the SPEC tradition of giving Java users the most objective and representative benchmarks for measuring a system's ability to run Java applications. SPEC has designed the SPECjAppServer2002 benchmark to exercise the Java Enterprise Application Server, the Java Virtual Machine (JVM), as well as the server Systems Under Test (SUT). As a true J2EE application the benchmark will require a functional RDBMS (for JDBC) and a Web Server, but the benchmark has been designed so that the SUT can be a single system. Please note that while the SPECjAppServer2002 suite is still available for purchase, the suite has been retired, no further results are being accepted for publication, and support is no longer provided.
", + "id" : 588, + "need_html_regen" : 0, + "_bibtex_type" : "misc", + "entry_type" : "paper", + "year" : 2002, + "html_bib" : "
\n@misc{sp2002-SPEC-SPECjAppServer2002,\n  abstract = {SPECjAppServer2002 (Java Application Server) is a client/server benchmark for measuring the performance of Java Enterprise Application Servers using a subset of J2EE APIs in a complete end-to-end web application. It is the same as SPECjAppServer2001 (released in September 2002) except that the Enterprise Java Beans (EJBs) are defined using the EJB 2.0 specification instead of the EJB 1.1 specification. SPECjAppServer2002 can therefore take advantage of several EJB 2.0 features such as local interfaces, the EJB-QL query language, and Container Managed Relationships (CMR) between entity beans. Joining the client-side SPECjvm98 and the server side SPECjbb2000, SPECjAppServer2002 and SPECjAppServer2001 continue the SPEC tradition of giving Java users the most objective and representative benchmarks for measuring a system's ability to run Java applications. SPEC has designed the SPECjAppServer2002 benchmark to exercise the Java Enterprise Application Server, the Java Virtual Machine (JVM), as well as the server Systems Under Test (SUT). As a true J2EE application the benchmark will require a functional RDBMS (for JDBC) and a Web Server, but the benchmark has been designed so that the SUT can be a single system. Please note that while the SPECjAppServer2002 suite is still available for purchase, the suite has been retired, no further results are being accepted for publication, and support is no longer provided.},\n  author = {SPEC},\n  howpublished = {Standard Performance Evaluation Corporation},\n  month = {November},\n  title = {{SPECjAppServer2002 - Industry-standard enterprise Java application server benchmark (J2EE 1.3).}},\n  url = {http://www.spec.org/jAppServer2002/},\n  year = {2002}\n}\n
\n\n", + "old_mysql_id" : 588, + "creation_time" : "2017-02-08T17:53:53", + "abstract" : "SPECjAppServer2002 (Java Application Server) is a client/server benchmark for measuring the performance of Java Enterprise Application Servers using a subset of J2EE APIs in a complete end-to-end web application. It is the same as SPECjAppServer2001 (released in September 2002) except that the Enterprise Java Beans (EJBs) are defined using the EJB 2.0 specification instead of the EJB 1.1 specification. SPECjAppServer2002 can therefore take advantage of several EJB 2.0 features such as local interfaces, the EJB-QL query language, and Container Managed Relationships (CMR) between entity beans. Joining the client-side SPECjvm98 and the server side SPECjbb2000, SPECjAppServer2002 and SPECjAppServer2001 continue the SPEC tradition of giving Java users the most objective and representative benchmarks for measuring a system's ability to run Java applications. SPEC has designed the SPECjAppServer2002 benchmark to exercise the Java Enterprise Application Server, the Java Virtual Machine (JVM), as well as the server Systems Under Test (SUT). As a true J2EE application the benchmark will require a functional RDBMS (for JDBC) and a Web Server, but the benchmark has been designed so that the SUT can be a single system. Please note that while the SPECjAppServer2002 suite is still available for purchase, the suite has been retired, no further results are being accepted for publication, and support is no longer provided.", + "hidden" : 0 + }, + { + "year" : 2011, + "html_bib" : "
\n@article{SwRyGr2011-EAT,\n  address = {Pozna{\\'n}, Poland},\n  author = {Pawel \\'{S}wi\\k{a}tek and Piotr Rygielski and Adam Grzech},\n  isbn = {2081-8580},\n  journal = {Advances in Electronics and Telecommunications},\n  month = {November},\n  number = {3},\n  pages = {50--54},\n  publisher = {Pozna{\\'n} University of Technology Press},\n  title = {{Resources Management and Services Personalization in Future Internet Applications}},\n  url = {http://www.advances.et.put.poznan.pl/},\n  volume = {2},\n  year = {2011}\n}\n
\n\n", + "old_mysql_id" : 675, + "creation_time" : "2017-02-08T17:53:54", + "abstract" : null, + "hidden" : 1, + "title" : "{Resources Management and Services Personalization in Future Internet Applications}", + "month" : 11, + "modified_time" : "2017-02-08T02:00:03", + "html" : "Pawel Świątek, Piotr Rygielski, and Adam Grzech.\nResources Management and Services Personalization in Future Internet Applications.\n Advances in Electronics and Telecommunications, 2(3):50–54, November 2011, Poznań University of Technology Press, Poznań, Poland.\n[ bib | http\n ]\n
@article{SwRyGr2011-EAT,\n  address = {Pozna{\\'n}, Poland},\n  author = {Pawel \\'{S}wi\\k{a}tek and Piotr Rygielski and Adam Grzech},\n  isbn = {2081-8580},\n  journal = {Advances in Electronics and Telecommunications},\n  month = {November},\n  number = {3},\n  pages = {50--54},\n  publisher = {Pozna{\\'n} University of Technology Press},\n  title = {{Resources Management and Services Personalization in Future Internet Applications}},\n  url = {http://www.advances.et.put.poznan.pl/},\n  volume = {2},\n  year = {2011},\n}\n\n
", + "bib" : "@article{SwRyGr2011-EAT,\n address = {Pozna{\\'n}, Poland},\n author = {Pawel \\'{S}wi\\k{a}tek and Piotr Rygielski and Adam Grzech},\n isbn = {2081-8580},\n journal = {Advances in Electronics and Telecommunications},\n month = {November},\n number = {3},\n pages = {50--54},\n publisher = {Pozna{\\'n} University of Technology Press},\n title = {{Resources Management and Services Personalization in Future Internet Applications}},\n url = {http://www.advances.et.put.poznan.pl/},\n volume = {2},\n year = {2011},\n}\n\n", + "bibtex_key" : "SwRyGr2011-EAT", + "id" : 675, + "entry_type" : "paper", + "need_html_regen" : 0, + "_bibtex_type" : "article" + }, + { + "bibtex_key" : "SPEC-RG-NEWSLETTER-4", + "bib" : "@misc{SPEC-RG-NEWSLETTER-4,\n author = {Samuel Kounev and Kai Sachs and Piotr Rygielski},\n month = {January},\n note = {Published by Standard Performance Evaluation Corporation (SPEC)},\n title = {{SPEC Research Group Newsletter, vol. 1 no. 4}},\n url = {http://research.spec.org/en/newsletter.html},\n year = {2015},\n pdf = {http://localhost:8083/publications/download/paper/781.pdf},\n}\n\n", + "html" : "\n\nSamuel Kounev, Kai Sachs, and Piotr Rygielski.\nSPEC Research Group Newsletter, vol. 1 no. 4, January 2015.\nPublished by Standard Performance Evaluation Corporation (SPEC).\n[ bib | .html | .pdf  ]
\n@misc{SPEC-RG-NEWSLETTER-4,\nauthor = {Samuel Kounev and Kai Sachs and Piotr Rygielski},\nmonth = {January},\nnote = {Published by Standard Performance Evaluation Corporation (SPEC)},\ntitle = {{SPEC Research Group Newsletter, vol. 1 no. 4}},\nurl = {http://research.spec.org/en/newsletter.html},\nyear = {2015},\npdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/781.pdf}\n}\n
\n
\n", + "_bibtex_type" : "misc", + "need_html_regen" : 0, + "entry_type" : "paper", + "id" : 781, + "title" : "{SPEC Research Group Newsletter, vol. 1 no. 4}", + "month" : 1, + "modified_time" : "2017-12-10T19:35:01", + "creation_time" : "2017-02-08T17:53:54", + "hidden" : 0, + "abstract" : null, + "html_bib" : "
\n@misc{SPEC-RG-NEWSLETTER-4,\n  author = {Samuel Kounev and Kai Sachs and Piotr Rygielski},\n  month = {January},\n  note = {Published by Standard Performance Evaluation Corporation (SPEC)},\n  title = {{SPEC Research Group Newsletter, vol. 1 no. 4}},\n  url = {http://research.spec.org/en/newsletter.html},\n  year = {2015},\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/781.pdf}\n}\n
\n\n", + "year" : 2015, + "old_mysql_id" : 781 + }, + { + "bibtex_key" : "KoBrMeBeKoKoRy2016-SE-tradeoffs_perf_prediction_approaches", + "bib" : "@inproceedings{KoBrMeBeKoKoRy2016-SE-tradeoffs_perf_prediction_approaches,\n author = {Samuel Kounev and Fabian Brosig and Philipp Meier and Steffen Becker and Anne Koziolek and Heiko Koziolek and Piotr Rygielski},\n title = {{Analysis of the Trade-offs in Different Modeling Approaches for Performance Prediction of Software Systems}},\n booktitle = {Software Engineering 2016 (SE 2016), Fachtagung des GI-Fachbereichs Softwaretechnik, 23.-26. M\\\"{a}rz 2016, Vienna, Austria},\n publisher = {GI},\n series = {Lecture Notes in Informatics (LNI)},\n address = {Vienna, Austria},\n days = {23-26},\n month = {February},\n year = {2016},\n pages = {47--48},\n titleaddon = {{(Talk Extended Abstract)}},\n http = {http://subs.emis.de/LNI/Proceedings/Proceedings252/article3.html},\n pdf = {http://localhost:8083/publications/download/paper/1077.pdf},\n slides = {http://localhost:8083/publications/download/slides/1077},\n}\n\n", + "html" : "\n\nSamuel Kounev, Fabian Brosig, Philipp Meier, Steffen Becker, Anne Koziolek,\nHeiko Koziolek, and Piotr Rygielski.\nAnalysis of the Trade-offs in Different Modeling Approaches for\nPerformance Prediction of Software Systems (Talk Extended Abstract).\nIn Software Engineering 2016 (SE 2016), Fachtagung des\nGI-Fachbereichs Softwaretechnik, 23.-26. März 2016, Vienna, Austria,\nFebruary 2016, Lecture Notes in Informatics (LNI), pages 47-48. GI, Vienna,\nAustria.\nFebruary 2016.\n[ bib | slides | .html | .pdf  ]
\n@inproceedings{KoBrMeBeKoKoRy2016-SE-tradeoffs_perf_prediction_approaches,\nauthor = {Samuel Kounev and Fabian Brosig and Philipp Meier and Steffen Becker and Anne Koziolek and Heiko Koziolek and Piotr Rygielski},\ntitle = {{Analysis of the Trade-offs in Different Modeling Approaches for Performance Prediction of Software Systems}},\nbooktitle = {Software Engineering 2016 (SE 2016), Fachtagung des GI-Fachbereichs Softwaretechnik, 23.-26. M\\\"{a}rz 2016, Vienna, Austria},\npublisher = {GI},\nseries = {Lecture Notes in Informatics (LNI)},\naddress = {Vienna, Austria},\ndays = {23-26},\nmonth = {February},\nyear = {2016},\npages = {47--48},\ntitleaddon = {{(Talk Extended Abstract)}},\nhttp = {http://subs.emis.de/LNI/Proceedings/Proceedings252/article3.html},\npdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/1077.pdf},\nslides = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/slides/1077}\n}\n
\n
\n", + "_bibtex_type" : "inproceedings", + "need_html_regen" : 0, + "entry_type" : "talk", + "id" : 1077, + "title" : "{Analysis of the Trade-offs in Different Modeling Approaches for Performance Prediction of Software Systems}", + "month" : 2, + "modified_time" : "2017-12-10T19:35:02", + "creation_time" : "2016-02-24T18:09:58", + "hidden" : 0, + "abstract" : null, + "html_bib" : "
\n@inproceedings{KoBrMeBeKoKoRy2016-SE-tradeoffs_perf_prediction_approaches,\n  author = {Samuel Kounev and Fabian Brosig and Philipp Meier and Steffen Becker and Anne Koziolek and Heiko Koziolek and Piotr Rygielski},\n  title = {{Analysis of the Trade-offs in Different Modeling Approaches for Performance Prediction of Software Systems}},\n  booktitle = {Software Engineering 2016 (SE 2016), Fachtagung des GI-Fachbereichs Softwaretechnik, 23.-26. M\\\"{a}rz 2016, Vienna, Austria},\n  publisher = {GI},\n  series = {Lecture Notes in Informatics (LNI)},\n  address = {Vienna, Austria},\n  days = {23-26},\n  month = {February},\n  year = {2016},\n  pages = {47--48},\n  titleaddon = {{(Talk Extended Abstract)}},\n  http = {http://subs.emis.de/LNI/Proceedings/Proceedings252/article3.html},\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/1077.pdf},\n  slides = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/slides/1077}\n}\n
\n\n", + "year" : 2016, + "old_mysql_id" : 1077 + }, + { + "old_mysql_id" : 1099, + "year" : 2015, + "html_bib" : "
\n@inproceedings{SpinnerZLUGHK-2015-RADIO-MemoryScaling,\n  author = {Simon Spinner and Xiaoyun Zhu and Lei Lu and Mustafa Uysal and Rean Griffith and Nikolas Herbst and Samuel Kounev},\n  title = {{Proactive Memory Scaling of Virtualized Applications}},\n  booktitle = {VMware R&D Innovation Offsite (RADIO)},\n  year = {2015},\n  month = {July}\n}\n
\n\n", + "abstract" : null, + "hidden" : 1, + "creation_time" : "2016-04-20T17:12:15", + "modified_time" : "2017-02-08T02:00:03", + "month" : 7, + "title" : "{Proactive Memory Scaling of Virtualized Applications}", + "id" : 1099, + "need_html_regen" : 0, + "_bibtex_type" : "inproceedings", + "entry_type" : "paper", + "bibtex_key" : "SpinnerZLUGHK-2015-RADIO-MemoryScaling", + "bib" : "@inproceedings{SpinnerZLUGHK-2015-RADIO-MemoryScaling,\n author = {Simon Spinner and Xiaoyun Zhu and Lei Lu and Mustafa Uysal and Rean Griffith and Nikolas Herbst and Samuel Kounev},\n title = {{Proactive Memory Scaling of Virtualized Applications}},\n booktitle = {VMware R&D Innovation Offsite (RADIO)},\n year = {2015},\n month = {July},\n}\n\n", + "html" : "\n\nSimon Spinner, Xiaoyun Zhu, Lei Lu, Mustafa Uysal, Rean Griffith, Nikolas\nHerbst, and Samuel Kounev.\nProactive Memory Scaling of Virtualized Applications.\nIn VMware R&D Innovation Offsite (RADIO), July 2015.\n[ bib  ]
\n@inproceedings{SpinnerZLUGHK-2015-RADIO-MemoryScaling,\nauthor = {Simon Spinner and Xiaoyun Zhu and Lei Lu and Mustafa Uysal and Rean Griffith and Nikolas Herbst and Samuel Kounev},\ntitle = {{Proactive Memory Scaling of Virtualized Applications}},\nbooktitle = {VMware R&D Innovation Offsite (RADIO)},\nyear = {2015},\nmonth = {July}\n}\n
\n
\n" + }, + { + "entry_type" : "paper", + "need_html_regen" : 0, + "_bibtex_type" : "article", + "id" : 1105, + "html" : "\n\nAleksandar Milenkoski, Alexandru Iosup, Samuel Kounev, Kai Sachs, Diane E.\nMularz, Jonathan A. Curtiss, Jason J. Ding, Florian Rosenberg, and Piotr\nRygielski.\nCUP: A Formalism for Expressing Cloud Usage Patterns for Experts and\nNon-Experts.\nIEEE Cloud Computing, 2016.\nTo Appear.\n[ bib  ]
\n@article{MiIoKoSaMuCuDiRoRy2016-IEEECC-cup,\nauthor = {Aleksandar Milenkoski and Alexandru Iosup and Samuel Kounev and Kai Sachs and Diane E. Mularz and Jonathan A. Curtiss and Jason J. Ding and Florian Rosenberg and Piotr Rygielski},\ntitle = {{CUP: A Formalism for Expressing Cloud Usage Patterns for Experts and Non-Experts}},\njournal = {IEEE Cloud Computing},\nyear = {2016},\nnote = {To Appear}\n}\n
\n
\n", + "bib" : "@article{MiIoKoSaMuCuDiRoRy2016-IEEECC-cup,\n author = {Aleksandar Milenkoski and Alexandru Iosup and Samuel Kounev and Kai Sachs and Diane E. Mularz and Jonathan A. Curtiss and Jason J. Ding and Florian Rosenberg and Piotr Rygielski},\n title = {{CUP: A Formalism for Expressing Cloud Usage Patterns for Experts and Non-Experts}},\n journal = {IEEE Cloud Computing},\n publisher = {IEEE},\n year = {2016},\n note = {To Appear},\n}\n\n", + "bibtex_key" : "MiIoKoSaMuCuDiRoRy2016-IEEECC-cup", + "modified_time" : "2017-02-08T02:00:03", + "title" : "{CUP: A Formalism for Expressing Cloud Usage Patterns for Experts and Non-Experts}", + "month" : 0, + "hidden" : 0, + "abstract" : null, + "creation_time" : "2016-05-09T11:05:03", + "old_mysql_id" : 1105, + "year" : 2016, + "html_bib" : "
\n@article{MiIoKoSaMuCuDiRoRy2016-IEEECC-cup,\n  author = {Aleksandar Milenkoski and Alexandru Iosup and Samuel Kounev and Kai Sachs and Diane E. Mularz and Jonathan A. Curtiss and Jason J. Ding and Florian Rosenberg and Piotr Rygielski},\n  title = {{CUP: A Formalism for Expressing Cloud Usage Patterns for Experts and Non-Experts}},\n  journal = {IEEE Cloud Computing},\n  year = {2016},\n  note = {To Appear}\n}\n
\n\n" + }, + { + "html_bib" : "
\n@incollection{HeAmAnGrMeSuKo2016-self,\n  title = {{Online Workload Forecasting}},\n  editor = {Samuel Kounev and Jeffrey O. Kephart and Xiaoyun Zhu and Aleksandar Milenkoski},\n  author = {Nikolas Herbst and Ayman Amin and Artur Andrzejak and Lars Grunske and Ole J. Mengshoel and Priya Sundararajan and Samuel Kounev},\n  booktitle = {{Self-Aware Computing Systems}},\n  publisher = {{Springer Verlag}},\n  address = {{Berlin Heidelberg, Germany}},\n  year = {2016},\n  note = {{To Appear}}\n}\n
\n\n", + "year" : 2017, + "old_mysql_id" : 1111, + "creation_time" : "2016-05-10T15:48:15", + "abstract" : "{This chapter gives a summary of the state-of-the-art approaches from different research fields that can be applied to continuously forecast future developments of time series data streams. More specifically, the input time series data contains continuously monitored metrics that quantify the amount of incoming workload units to a self-aware system. It is the goal of this chapter to identify and present approaches for online workload forecasting that are required for a self-aware system to act proactively in terms of problem prevention and optimization inferred from likely changes in their usage. The research fields covered are machine learning and time series analysis. We describe explicit limitations and advantages for each forecasting method.}", + "hidden" : 0, + "title" : "{Online Workload Forecasting}", + "month" : 0, + "modified_time" : "2017-02-08T02:00:03", + "bibtex_key" : "HeAmAnGrMeSuKo2016-self", + "bib" : "@incollection{HeAmAnGrMeSuKo2016-self,\r\n title = {{Online Workload Forecasting}},\r\n editor = {Samuel Kounev and Jeffrey O. Kephart and Xiaoyun Zhu and Aleksandar Milenkoski},\r\n author = {Nikolas Herbst and Ayman Amin and Artur Andrzejak and Lars Grunske and Samuel Kounev and Ole J. Mengshoel and Priya Sundararajan},\r\n booktitle = {{Self-Aware Computing Systems}},\r\n publisher = {{Springer Verlag}},\r\n address = {{Berlin Heidelberg, Germany}},\r\n year = {2017},\r\n note = {{To Appear}},\r\nabstract={{This chapter gives a summary of the state-of-the-art approaches from different research fields that can be applied to continuously forecast future developments of time series data streams. More specifically, the input time series data contains continuously monitored metrics that quantify the amount of incoming workload units to a self-aware system. It is the goal of this chapter to identify and present approaches for online workload forecasting that are required for a self-aware system to act proactively in terms of problem prevention and optimization inferred from likely changes in their usage. The research fields covered are machine learning and time series analysis. We describe explicit limitations and advantages for each forecasting method.}}\r\n}", + "html" : "Nikolas Herbst, Ayman Amin, Artur Andrzejak, Lars Grunske, Samuel Kounev, Ole J. Mengshoel, and Priya Sundararajan.\nOnline Workload Forecasting.\nIn Self-Aware Computing Systems, Samuel Kounev, Jeffrey O. Kephart, Xiaoyun Zhu, and Aleksandar Milenkoski, editors. Springer Verlag, Berlin Heidelberg, Germany, 2017.\nTo Appear.\n[ bib | Abstract\n ]\n
@incollection{HeAmAnGrMeSuKo2016-self,\r\n  title = {{Online Workload Forecasting}},\r\n  editor = {Samuel Kounev and Jeffrey O. Kephart and Xiaoyun Zhu and Aleksandar Milenkoski},\r\n  author = {Nikolas Herbst and Ayman Amin and Artur Andrzejak and Lars Grunske and Samuel Kounev and Ole J. Mengshoel and Priya Sundararajan},\r\n  booktitle = {{Self-Aware Computing Systems}},\r\n  publisher = {{Springer Verlag}},\r\n  address = {{Berlin Heidelberg, Germany}},\r\n  year = {2017},\r\n  note = {{To Appear}},\r\nabstract={{This chapter gives a summary of the state-of-the-art approaches from different research fields that can be applied to continuously forecast future developments of time series data streams. More specifically, the input time series data contains continuously monitored metrics that quantify the amount of incoming workload units to a self-aware system. It is the goal of this chapter to identify and present approaches for online workload forecasting that are required for a self-aware system to act proactively in terms of problem prevention and optimization inferred from likely changes in their usage. The research fields covered are machine learning and time series analysis. We describe explicit limitations and advantages for each forecasting method.}}\r\n}
\n
{This chapter gives a summary of the state-of-the-art approaches from different research fields that can be applied to continuously forecast future developments of time series data streams. More specifically, the input time series data contains continuously monitored metrics that quantify the amount of incoming workload units to a self-aware system. It is the goal of this chapter to identify and present approaches for online workload forecasting that are required for a self-aware system to act proactively in terms of problem prevention and optimization inferred from likely changes in their usage. The research fields covered are machine learning and time series analysis. We describe explicit limitations and advantages for each forecasting method.}
", + "id" : 1111, + "_bibtex_type" : "incollection", + "need_html_regen" : 0, + "entry_type" : "paper" + }, + { + "title" : "{Reliable Resource Demand Estimation}", + "month" : 10, + "modified_time" : "2017-12-10T19:35:02", + "bibtex_key" : "grohmann16", + "bib" : "@mastersthesis{grohmann16,\n author = {Johannes Grohmann},\n abstract = {Resource demands are key parameters of performance models used to predict the behavior of data centers. They define the amount of time a request spends obtaining a limited resource like the CPU. Requests can be grouped into different workload classes. Measuring these resource demands is usually unfeasible in practice. Therefore, several different approaches to estimate the resource demands of different workload classes exist. However, different use-cases with individual properties influence the accuracy of the estimators. Among others the number of different workload classes to estimate is known to have an impact on the solution quality, but affects some approaches more than others. Additionally, most approaches offer specific parameters to configure and optimize the estimators. Nevertheless, in order to optimize the parameters of one estimation approach or to choose the best estimator for a given scenario either expert knowledge or exhaustive testing is required. While some works on comparing different approaches and configurations exist, we extend this by learning on a given training set and specially adapting the estimation approaches in order to optimize performance for the required target scenario. We simplify automated resource demand estimation by designing a framework for ready-to-use reliable resource demand estimation. In order to do so, we develop generic algorithms that can be used to autonomously optimize parameter configurations of black-box estimation approaches on a given training set. Secondly, machine learning algorithms analyze the behavior of the resource demand estimators on different training traces and automatically pick the best approach for a prior unseen trace. The framework is modularized and configurable and can be trained on any kind of trace data. We implement different algorithms for optimization as well as machine learning and evaluate them on a training set containing measurements of a real system. The results show that parameter optimization is very promising and can increase the accuracy of single approaches of up to 10%. When recommending one approach as opposed to running all simultaneously, comparable results can be achieved, while saving more than 50% of the runtime. However, a combination of both approaches does not seem useful on our data set.},\n title = {{Reliable Resource Demand Estimation}},\n year = {2016},\n month = {October},\n school = {University of W\\\"{u}rzburg},\n address = {Am Hubland, Informatikgeb\\\"aude, 97074 W\\\"urzburg, Germany},\n type = {{Master Thesis}},\n keywords = {Resource Demand Estimation, Machine Learning, Optimization, Meta-Learning},\n pdf = {http://localhost:8083/publications/download/paper/1159.pdf},\n slides = {http://localhost:8083/publications/download/slides/1159},\n}\n\n", + "html" : "Johannes Grohmann.\nReliable Resource Demand Estimation.\nMaster Thesis, University of Würzburg, Am Hubland, Informatikgebäude, 97074 Würzburg, Germany, October 2016.\n[ bib | Abstract\n | pdf\n | slides\n ]\n
@mastersthesis{grohmann16,\r\n  author = {Johannes Grohmann},\r\n  abstract = {Resource demands are key parameters of performance models used to predict the behavior of data centers. They define the amount of time a request spends obtaining a limited resource like the CPU. Requests can be grouped into different workload classes. Measuring these resource demands is usually unfeasible in practice. Therefore, several different approaches to estimate the resource demands of different workload classes exist. However, different use-cases with individual properties influence the accuracy of the estimators. Among others the number of different workload classes to estimate is known to have an impact on the solution quality, but affects some approaches more than others. Additionally, most approaches offer specific parameters to configure and optimize the estimators. Nevertheless, in order to optimize the parameters of one estimation approach or to choose the best estimator for a given scenario either expert knowledge or exhaustive testing is required. While some works on comparing different approaches and configurations exist, we extend this by learning on a given training set and specially adapting the estimation approaches in order to optimize performance for the required target scenario. We simplify automated resource demand estimation by designing a framework for ready-to-use reliable resource demand estimation. In order to do so, we develop generic algorithms that can be used to autonomously optimize parameter configurations of black-box estimation approaches on a given training set. Secondly, machine learning algorithms analyze the behavior of the resource demand estimators on different training traces and automatically pick the best approach for a prior unseen trace. The framework is modularized and configurable and can be trained on any kind of trace data. We implement different algorithms for optimization as well as machine learning and evaluate them on a training set containing measurements of a real system. The results show that parameter optimization is very promising and can increase the accuracy of single approaches of up to 10%. When recommending one approach as opposed to running all simultaneously, comparable results can be achieved, while saving more than 50% of the runtime. However, a combination of both approaches does not seem useful on our data set.},\r\n  title = {{Reliable Resource Demand Estimation}},\r\n  year = {2016},\r\n  month = {October},\r\n  school = {University of W\\\"{u}rzburg},\r\n  address = {Am Hubland, Informatikgeb\\\"aude, 97074 W\\\"urzburg, Germany},\r\n  type = {{Master Thesis}},\r\n  keywords = {Resource Demand Estimation, Machine Learning, Optimization, Meta-Learning},\r\n  pdf = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/paper/1159.pdf},\r\n  slides = {http://se2.informatik.uni-wuerzburg.de/pa/publications/download/slides/1159},\r\n}
\n
Resource demands are key parameters of performance models used to predict the behavior of data centers. They define the amount of time a request spends obtaining a limited resource like the CPU. Requests can be grouped into different workload classes. Measuring these resource demands is usually unfeasible in practice. Therefore, several different approaches to estimate the resource demands of different workload classes exist. However, different use-cases with individual properties influence the accuracy of the estimators. Among others the number of different workload classes to estimate is known to have an impact on the solution quality, but affects some approaches more than others. Additionally, most approaches offer specific parameters to configure and optimize the estimators. Nevertheless, in order to optimize the parameters of one estimation approach or to choose the best estimator for a given scenario either expert knowledge or exhaustive testing is required. While some works on comparing different approaches and configurations exist, we extend this by learning on a given training set and specially adapting the estimation approaches in order to optimize performance for the required target scenario. We simplify automated resource demand estimation by designing a framework for ready-to-use reliable resource demand estimation. In order to do so, we develop generic algorithms that can be used to autonomously optimize parameter configurations of black-box estimation approaches on a given training set. Secondly, machine learning algorithms analyze the behavior of the resource demand estimators on different training traces and automatically pick the best approach for a prior unseen trace. The framework is modularized and configurable and can be trained on any kind of trace data. We implement different algorithms for optimization as well as machine learning and evaluate them on a training set containing measurements of a real system. The results show that parameter optimization is very promising and can increase the accuracy of single approaches of up to 10%. When recommending one approach as opposed to running all simultaneously, comparable results can be achieved, while saving more than 50% of the runtime. However, a combination of both approaches does not seem useful on our data set.
", + "_bibtex_type" : "mastersthesis", + "need_html_regen" : 0, + "entry_type" : "paper", + "id" : 1159, + "html_bib" : null, + "year" : 2016, + "old_mysql_id" : 1159, + "creation_time" : "2016-11-07T14:22:45", + "hidden" : 0, + "abstract" : "Resource demands are key parameters of performance models used to predict the behavior of data centers. They define the amount of time a request spends obtaining a limited resource like the CPU. Requests can be grouped into different workload classes. Measuring these resource demands is usually unfeasible in practice. Therefore, several different approaches to estimate the resource demands of different workload classes exist. However, different use-cases with individual properties influence the accuracy of the estimators. Among others the number of different workload classes to estimate is known to have an impact on the solution quality, but affects some approaches more than others. Additionally, most approaches offer specific parameters to configure and optimize the estimators. Nevertheless, in order to optimize the parameters of one estimation approach or to choose the best estimator for a given scenario either expert knowledge or exhaustive testing is required. While some works on comparing different approaches and configurations exist, we extend this by learning on a given training set and specially adapting the estimation approaches in order to optimize performance for the required target scenario. We simplify automated resource demand estimation by designing a framework for ready-to-use reliable resource demand estimation. In order to do so, we develop generic algorithms that can be used to autonomously optimize parameter configurations of black-box estimation approaches on a given training set. Secondly, machine learning algorithms analyze the behavior of the resource demand estimators on different training traces and automatically pick the best approach for a prior unseen trace. The framework is modularized and configurable and can be trained on any kind of trace data. We implement different algorithms for optimization as well as machine learning and evaluate them on a training set containing measurements of a real system. The results show that parameter optimization is very promising and can increase the accuracy of single approaches of up to 10%. When recommending one approach as opposed to running all simultaneously, comparable results can be achieved, while saving more than 50% of the runtime. However, a combination of both approaches does not seem useful on our data set." + }, + { + "hidden" : 1, + "abstract" : null, + "creation_time" : "2016-12-07T10:20:56", + "old_mysql_id" : 1166, + "year" : 2017, + "html_bib" : null, + "entry_type" : "paper", + "_bibtex_type" : "phdthesis", + "need_html_regen" : 0, + "id" : 1166, + "html" : "Piotr Rygielski.\n Flexible Modeling Data Center Networks for Capacity Management.\nPhD thesis, University of Würzburg, Germany, 2017.\n[ bib ]\n
@phdthesis{Rygielski2017-phd,\r\nauthor = {Piotr Rygielski},\r\ntitle = {Flexible Modeling Data Center Networks for Capacity Management},\r\nyear = {2017},\r\nmonth = {},\r\nschool = {University of W\\\"{u}rzburg, Germany},\r\n}
", + "bibtex_key" : "Rygielski2017-phd", + "bib" : "@phdthesis{Rygielski2017-phd,\r\nauthor = {Piotr Rygielski},\r\ntitle = {Flexible Modeling Data Center Networks for Capacity Management},\r\nyear = {2017},\r\nmonth = {},\r\nschool = {University of W\\\"{u}rzburg, Germany},\r\n}", + "modified_time" : "2017-02-08T02:00:03", + "title" : "Flexible Modeling Data Center Networks for Capacity Management", + "month" : 0 + }, + { + "id" : 1173, + "_bibtex_type" : "article", + "need_html_regen" : 0, + "entry_type" : "paper", + "bib" : "@article{key2017,\r\n author = {Johny Example},\r\n journal = {Journal of this and that},\r\n publisher = {Printer-at-home publishing},\r\n title = {{Selected aspects of some methods}},\r\n year = {2017},\r\n month = {August},\r\n day = {1--31},\r\n }", + "bibtex_key" : "key2017", + "html" : "Johny Example.\nSelected aspects of some methods.\n Journal of this and that, August 2017, Printer-at-home publishing.\n[ bib ]\n
@article{key2017,\n  author = {Johny Example},\n  journal = {Journal of this and that},\n  publisher = {Printer-at-home publishing},\n  title = {{Selected aspects of some methods}},\n  year = {2017},\n  month = {August},\n  day = {1--31},\n}\n\n
", + "modified_time" : "2017-08-08T08:19:26", + "title" : "{Selected aspects of some methods}", + "month" : 8, + "abstract" : null, + "hidden" : 0, + "creation_time" : "2017-08-08T08:19:26", + "old_mysql_id" : 1173, + "html_bib" : null, + "year" : 2017 + }, + { + "month" : 8, + "title" : "{Selected aspects of some methods}", + "modified_time" : "2018-07-15T17:36:02", + "bib" : "@article{key2018-invisible,\r\n author = {Johny Example and Henry Invisible},\r\n journal = {Journal of this and that},\r\n publisher = {Printer-at-home publishing},\r\n title = {{Selected aspects of some methods}},\r\n year = {2018},\r\n month = {August},\r\n day = {1--31},\r\n }", + "bibtex_key" : "key2018-invisible", + "html" : "Johny Example, and Henry Invisible.\nSelected aspects of some methods.\n Journal of this and that, August 2018, Printer-at-home publishing.\n[ bib ]\n
@article{key2018-invisible,\n  author = {Johny Example and Henry Invisible},\n  journal = {Journal of this and that},\n  publisher = {Printer-at-home publishing},\n  title = {{Selected aspects of some methods}},\n  year = {2018},\n  month = {August},\n  day = {1--31},\n}\n\n
", + "id" : 1174, + "_bibtex_type" : "article", + "need_html_regen" : 0, + "entry_type" : "paper", + "year" : 2018, + "html_bib" : null, + "old_mysql_id" : 1174, + "creation_time" : "2018-07-15T17:36:02", + "abstract" : null, + "hidden" : 0 + }, + { + "creation_time" : "2018-07-15T21:14:47", + "abstract" : null, + "hidden" : 0, + "html_bib" : null, + "year" : 2018, + "old_mysql_id" : 1175, + "html" : "Master Test, and Minion Test.\nSelected aspects of some methods.\n Journal of this and that, July 2018, Printer-at-home publishing.\n[ bib ]\n
@article{key2018-master-minion,\n  author = {Master Test and Minion Test},\n  journal = {Journal of this and that},\n  publisher = {Printer-at-home publishing},\n  title = {{Selected aspects of some methods}},\n  year = {2018},\n  month = {July},\n  day = {1--31},\n}\n\n
", + "bib" : "@article{key2018-master-minion,\r\n author = {Master Test and Minion Test},\r\n journal = {Journal of this and that},\r\n publisher = {Printer-at-home publishing},\r\n title = {{Selected aspects of some methods}},\r\n year = {2018},\r\n month = {July},\r\n day = {1--31},\r\n }", + "bibtex_key" : "key2018-master-minion", + "id" : 1175, + "entry_type" : "paper", + "need_html_regen" : 0, + "_bibtex_type" : "article", + "month" : 7, + "title" : "{Selected aspects of some methods}", + "modified_time" : "2018-07-15T21:14:47" + }, + { + "bib" : "@article{key2018-master-only,\r\n author = {Master Test},\r\n journal = {Journal of this and that},\r\n publisher = {Printer-at-home publishing},\r\n title = {{Selected aspects of some methods}},\r\n year = {2018},\r\n month = {July},\r\n day = {1--31},\r\n }", + "bibtex_key" : "key2018-master-only", + "html" : "Master Test.\nSelected aspects of some methods.\n Journal of this and that, July 2018, Printer-at-home publishing.\n[ bib ]\n
@article{key2018-master-only,\n  author = {Master Test},\n  journal = {Journal of this and that},\n  publisher = {Printer-at-home publishing},\n  title = {{Selected aspects of some methods}},\n  year = {2018},\n  month = {July},\n  day = {1--31},\n}\n\n
", + "need_html_regen" : 0, + "_bibtex_type" : "article", + "entry_type" : "paper", + "id" : 1176, + "month" : 7, + "title" : "{Selected aspects of some methods}", + "modified_time" : "2018-07-15T21:15:03", + "creation_time" : "2018-07-15T21:15:03", + "hidden" : 0, + "abstract" : null, + "html_bib" : null, + "year" : 2018, + "old_mysql_id" : 1176 + }, + { + "abstract" : null, + "hidden" : 0, + "creation_time" : "2018-07-15T21:15:17", + "old_mysql_id" : 1177, + "html_bib" : null, + "year" : 2018, + "id" : 1177, + "need_html_regen" : 0, + "_bibtex_type" : "article", + "entry_type" : "paper", + "bibtex_key" : "key2018-minion-only", + "bib" : "@article{key2018-minion-only,\r\n author = {Minion Test},\r\n journal = {Journal of this and that},\r\n publisher = {Printer-at-home publishing},\r\n title = {{Selected aspects of some methods}},\r\n year = {2018},\r\n month = {July},\r\n day = {1--31},\r\n }", + "html" : "Minion Test.\nSelected aspects of some methods.\n Journal of this and that, July 2018, Printer-at-home publishing.\n[ bib ]\n
@article{key2018-minion-only,\n  author = {Minion Test},\n  journal = {Journal of this and that},\n  publisher = {Printer-at-home publishing},\n  title = {{Selected aspects of some methods}},\n  year = {2018},\n  month = {July},\n  day = {1--31},\n}\n\n
", + "modified_time" : "2018-07-15T21:15:17", + "month" : 7, + "title" : "{Selected aspects of some methods}" + } + ], + "Team" : [ + { + "old_mysql_id" : 1, + "id" : 1, + "parent" : null, + "name" : "SE-WUERZBURG" + }, + { + "name" : "PiotrPL", + "parent" : null, + "id" : 3, + "old_mysql_id" : 3 + }, + { + "old_mysql_id" : 4, + "id" : 4, + "name" : "DESCARTES_ALUMNI", + "parent" : null + }, + { + "parent" : null, + "name" : "DESCARTES", + "id" : 6, + "old_mysql_id" : 6 + } + ], + "Authorship" : [ + { + "entry_id" : 435, + "author_id" : 1389 + }, + { + "author_id" : 591, + "entry_id" : 588 + }, + { + "author_id" : 527, + "entry_id" : 675 + }, + { + "entry_id" : 781, + "author_id" : 527 + }, + { + "entry_id" : 1077, + "author_id" : 527 + }, + { + "entry_id" : 1099, + "author_id" : 1121 + }, + { + "entry_id" : 1105, + "author_id" : 527 + }, + { + "author_id" : 1389, + "entry_id" : 1105 + }, + { + "entry_id" : 1111, + "author_id" : 1121 + }, + { + "entry_id" : 1159, + "author_id" : 1486 + }, + { + "entry_id" : 1166, + "author_id" : 527 + }, + { + "author_id" : 1487, + "entry_id" : 1173 + }, + { + "entry_id" : 1174, + "author_id" : 1487 + }, + { + "entry_id" : 1174, + "author_id" : 1489 + }, + { + "author_id" : 1490, + "entry_id" : 1175 + }, + { + "author_id" : 1491, + "entry_id" : 1175 + }, + { + "entry_id" : 1176, + "author_id" : 1490 + }, + { + "entry_id" : 1177, + "author_id" : 1491 + } + ], + "Exception" : [ + { + "entry_id" : 588, + "team_id" : 1 + }, + { + "team_id" : 6, + "entry_id" : 588 + }, + { + "team_id" : 6, + "entry_id" : 1173 + } + ], + "TagType" : [ + { + "old_mysql_id" : 1, + "id" : 1, + "name" : "Tag", + "comment" : "keyword" + }, + { + "comment" : "12 categories defined as in research agenda", + "name" : "Category", + "id" : 2, + "old_mysql_id" : 2 + }, + { + "name" : "Other", + "comment" : "Reserved for other grouppings of papers (e.g., QPME_Bibliography)", + "id" : 3, + "old_mysql_id" : 3 + }, + { + "id" : 4, + "old_mysql_id" : 4, + "name" : "Imported", + "comment" : "Tags Imported from Bibtex" + } + ] + }, + "dateTimeFormat" : "%Y-%m-%dT%T", + "formatVersion" : "1" +} diff --git a/lib/BibSpace.pm b/lib/BibSpace.pm index b78ce37..df80e79 100644 --- a/lib/BibSpace.pm +++ b/lib/BibSpace.pm @@ -1,4 +1,4 @@ -package BibSpace v0.5.3; +package BibSpace v0.6.0; # ABSTRACT: BibSpace is a system to manage Bibtex references for authors and research groups web page. @@ -15,35 +15,27 @@ use Mojo::Base 'Mojolicious::Plugin::Config'; use Data::Dumper; -# use File::Slurp; use POSIX qw/strftime/; use Try::Tiny; use Path::Tiny; # for creating directories use Mojo::Home; use File::Spec; -use BibSpace::Backend::SmartArray; -use BibSpace::Backend::SmartHash; -use BibSpace::Backend::SmartBackendHelper; use BibSpace::Util::SimpleLogger; -use BibSpace::Util::SmartUidProvider; -use BibSpace::Util::DummyUidProvider; use BibSpace::Util::Statistics; use BibSpace::Model::User; use BibSpace::DAO::DAOFactory; -use BibSpace::Repository::LayeredRepository; +use BibSpace::Repository::FlatRepository; use BibSpace::Repository::RepositoryLayer; -use BibSpace::Repository::RepositoryFacade; +use BibSpace::Repository::FlatRepositoryFacade; use BibSpace::Converter::IHtmlBibtexConverter; use BibSpace::Converter::Bibtex2HtmlConverter; use BibSpace::Converter::BibStyleConverter; -use Storable; - use BibSpace::Util::Preferences; use BibSpace::Util::EntityFactory; @@ -121,106 +113,54 @@ has get_log_dir => sub { # }; has version => sub { - return $BibSpace::VERSION // "0.5.0"; -}; - -has quick_load_fixture_filename => sub { - my $self = shift; - return $self->app->home->rel_file('bibspace.dat'); -}; - -# don't want to read data form DB and wait to link them every reload? -# use quick_load_fixture! Useful for development and testing. -# better disable it for production -has use_quick_load_fixture => sub { - my $self = shift; - return if $self->mode eq 'production'; - - return 1 if defined $ENV{BIBSPACE_USE_DUMP} and $ENV{BIBSPACE_USE_DUMP} == 1; - return; + return $BibSpace::VERSION // "0.6.0"; }; -# please use only a single type of logger at once. -# Using multiple may not be supported currently -# if you really want to use multiple different loggers or state-full loggers (please don't), -# then you need to move the object construction INTO the LayeredReposity and provide a helper to access it for everywhere. +# Using multiple is not be supported currently. +# Use only stateless loggers. has logger => sub { state $logger = SimpleLogger->new() }; -has smartArrayBackend => sub { - my $self = shift; - return SmartArray->new(logger => $self->logger); -}; - ## I moved this to helpers as app->attr for a while -has layeredRepository => sub { +has flatRepository => sub { my $self = shift; - $self->app->logger->info("Building layeredRepository"); + $self->app->logger->info("Building flatRepository"); + + my $mySQLLayer = RepositoryLayer->new( + name => 'mysql', + priority => 99, + creates_on_read => 1, + backendFactoryName => "MySQLDAOFactory", + logger => $self->logger, + handle => $self->db, + reset_data_callback => \&reset_db_data, + reset_data_callback_arguments => [$self->db], + ); - my $LR = LayeredRepository->new( + return FlatRepository->new( logger => $self->logger, preferences => $self->preferences, - - # id_provider_class => 'DummyUidProvider', - id_provider_class => 'IntegerUidProvider', + layer => $mySQLLayer ); - - my $smartArrayLayer = RepositoryLayer->new( - name => 'smart', - priority => 1, - creates_on_read => undef, - backendFactoryName => "SmartArrayDAOFactory", - logger => $self->logger, - handle => $self->smartArrayBackend, - -# reset_data_callback must be undef if you want to create and restore backups using Storable. - reset_data_callback => undef, - is_read => 1 - ); - $LR->add_layer($smartArrayLayer); - - if (!$self->db) { - $self->logger->error( - "You add SQL layer, but there is no connection to the database! Skipping this layer." - . " You need to start MySQL server and restart BibSpace to use this layer" - ); - } - else { - my $mySQLLayer = RepositoryLayer->new( - name => 'mysql', - priority => 99, - creates_on_read => 1, - backendFactoryName => "MySQLDAOFactory", - logger => $self->logger, - handle => $self->db, - reset_data_callback => \&reset_db_data, - reset_data_callback_arguments => [$self->db], - ); - $LR->add_layer($mySQLLayer); - } - return $LR; }; -# layeredRepository will not change at runtime => repo neither. has repo => sub { my $self = shift; - return RepositoryFacade->new(lr => $self->layeredRepository); - + return FlatRepositoryFacade->new(lr => $self->flatRepository); }; sub startup { my $self = shift; $self->app->logger->info("*** Starting BibSpace ***"); - $self->setup_config; $self->setup_plugins; + create_main_db($self->app->db); $self->app->preferences->local_time_zone( DateTime::TimeZone->new(name => 'local')->name); $self->setup_routes; $self->setup_hooks; - $self->setup_repositories; $self->insert_admin; $self->app->logger->info("Setup done."); @@ -260,57 +200,6 @@ sub insert_admin { return; } -sub setup_repositories { - my $self = shift; - - $self->app->logger->info("Setup repositories..."); - - if (-e $self->quick_load_fixture_filename and $self->use_quick_load_fixture) { - -# $self->app->logger->info("Retrieving dump from '".$self->quick_load_fixture_filename."'."); - my $layer = retrieve($self->quick_load_fixture_filename); - - # reser read layer = not needed, layer empty by start of the app - # $self->app->logger->info("Replacing layer 'smart' with the dump."); - $self->repo->lr->replace_layer('smart', $layer); - -# $self->app->logger->debug("State after replacement:".$self->repo->lr->get_summary_table); - } - else { - $self->app->logger->info( - "We do not use dump file '" . $self->quick_load_fixture_filename . "'."); - } - - # no data, no fun = no need to copy, link, and store - if ($self->repo->entries_empty) { - $self->app->logger->info("Repo has no entries. Reseting read_layer."); - - $self->repo->lr->copy_data({from => 'mysql', to => 'smart'}); - - # Entities and Relations in the smart layer must be linked! - $self->link_data; - - $self->app->logger->info("Storing current state to dump file '" - . $self->quick_load_fixture_filename - . "'."); - - # store current state to file - store $self->repo->lr->get_read_layer, $self->quick_load_fixture_filename; - } - return; -} - -sub link_data { - my $self = shift; - $self->app->logger->info("Linking data..."); - BibSpace::Backend::SmartBackendHelper::linkData($self->app); - - # BackendHelper::linkData($self->app); - # linkData($self->app); - $self->app->logger->info("Linking Finished."); - return; -} - sub setup_config { my $self = shift; my $app = $self; @@ -377,9 +266,9 @@ sub setup_routes { my $anyone = $self->routes; $anyone->get('/')->to('display#index')->name('start'); - $anyone->get('/forgot')->to('login#forgot'); + $anyone->get('/forgot')->to('login#forgot')->name('forgot_password'); $anyone->post('/forgot/gen')->to('login#post_gen_forgot_token'); - $anyone->get('/forgot/reset/:token')->to('login#token_clicked') + $anyone->get('/forgot/reset/<:token>')->to('login#token_clicked') ->name("token_clicked"); $anyone->post('/forgot/store')->to('login#store_password'); @@ -397,7 +286,8 @@ sub setup_routes { $anyone->get('/register')->to('login#register')->name('register'); $anyone->post('/register')->to('login#post_do_register') ->name('post_do_register'); - $anyone->any('/noregister')->to('login#register_disabled'); + $anyone->any('/noregister')->to('login#register_disabled') + ->name('registration_disabled'); my $logged_user = $anyone->under->to('login#check_is_logged_in'); my $manager_user = $logged_user->under->to('login#under_check_is_manager'); @@ -418,13 +308,7 @@ sub setup_routes { ->name('load_fixture'); $admin_user->get('/persistence/save')->to('persistence#save_fixture') ->name('save_fixture'); - $admin_user->get('/persistence/copy_mysql_to_smart') - ->to('persistence#copy_mysql_to_smart')->name('copy_mysql_to_smart'); - $admin_user->get('/persistence/copy_smart_to_mysql') - ->to('persistence#copy_smart_to_mysql')->name('copy_smart_to_mysql'); - $admin_user->get('/persistence/persistence_status') - ->to('persistence#persistence_status')->name('persistence_status'); $admin_user->get('/persistence/persistence_status_ajax') ->to('persistence#persistence_status_ajax') ->name('persistence_status_ajax'); @@ -436,23 +320,20 @@ sub setup_routes { $admin_user->get('/persistence/reset_all')->to('persistence#reset_all') ->name('reset_all'); - $admin_user->get('/persistence/insert_random_data') - ->to('persistence#insert_random_data')->name('insert_random_data'); - ################ SETTINGS ################ - $logged_user->get('/profile')->to('login#profile'); + $logged_user->get('/profile')->to('login#profile')->name('show_my_profile'); $admin_user->get('/manage_users')->to('login#manage_users') ->name('manage_users'); - $admin_user->get('/profile/:id')->to('login#foreign_profile') + $admin_user->get('/profile/<:id>')->to('login#foreign_profile') ->name('show_user_profile'); - $admin_user->get('/profile/delete/:id')->to('login#delete_user') + $admin_user->get('/profile/delete/<:id>')->to('login#delete_user') ->name('delete_user'); - $admin_user->get('/profile/make_user/:id')->to('login#make_user') + $admin_user->get('/profile/make_user/<:id>')->to('login#make_user') ->name('make_user'); - $admin_user->get('/profile/make_manager/:id')->to('login#make_manager') + $admin_user->get('/profile/make_manager/<:id>')->to('login#make_manager') ->name('make_manager'); - $admin_user->get('/profile/make_admin/:id')->to('login#make_admin') + $admin_user->get('/profile/make_admin/<:id>')->to('login#make_admin') ->name('make_admin'); $manager_user->get('/log')->to('display#show_log')->name('show_log'); @@ -460,9 +341,9 @@ sub setup_routes { ->name('show_stats'); # websocket for fun - $manager_user->websocket('/log_websocket/:num')->to('display#show_log_ws') + $manager_user->websocket('/log_websocket/<:num>')->to('display#show_log_ws') ->name('show_log_websocket'); - $manager_user->websocket('/statistics/:num') + $manager_user->websocket('/statistics/<:num>') ->to('display#show_stats_websocket')->name('show_stats_websocket'); $admin_user->get('/settings/fix_months')->to('publications#fixMonths') @@ -474,7 +355,7 @@ sub setup_routes { $manager_user->get('/settings/mark_all_to_regenerate') ->to('publications#mark_all_to_regenerate')->name('mark_all_to_regenerate'); - $manager_user->get('/settings/mark_author_to_regenerate/:author_id') + $manager_user->get('/settings/mark_author_to_regenerate/<:author_id>') ->to('publications#mark_author_to_regenerate') ->name('mark_author_to_regenerate'); @@ -482,22 +363,24 @@ sub setup_routes { ->to('publications#regenerate_html_for_all') ->name('regenerate_html_for_all'); - $logged_user->get('/settings/regenerate_html_in_chunk/:chunk_size') + $logged_user->get('/settings/regenerate_html_in_chunk/<:chunk_size>') ->to('publications#regenerate_html_in_chunk') ->name('regenerate_html_in_chunk'); $manager_user->get('/backups')->to('backup#index')->name('backup_index'); + + # Do default backup $manager_user->put('/backups')->to('backup#save')->name('backup_do'); $manager_user->put('/backups/mysql')->to('backup#save_mysql') ->name('backup_do_mysql'); $manager_user->put('/backups/json')->to('backup#save_json') ->name('backup_do_json'); - $manager_user->get('/backups/:id')->to('backup#backup_download') + $manager_user->get('/backups/<:id>')->to('backup#backup_download') ->name('backup_download'); - $admin_user->delete('/backups/:id')->to('backup#delete_backup') + $admin_user->delete('/backups/<:id>')->to('backup#delete_backup') ->name('backup_delete'); - $admin_user->put('/backups/:id')->to('backup#restore_backup') + $admin_user->put('/backups/<:id>')->to('backup#restore_backup') ->name('backup_restore'); $admin_user->delete('/backups')->to('backup#cleanup')->name('backup_cleanup'); @@ -506,19 +389,19 @@ sub setup_routes { $manager_user->get('/types/add')->to('types#add_type')->name('add_type_get'); $manager_user->post('/types/add')->to('types#post_add_type') ->name('add_type_post'); - $manager_user->get('/types/manage/:name')->to('types#manage') + $manager_user->get('/types/manage/<:name>')->to('types#manage') ->name('edit_type'); - $manager_user->get('/types/delete/:name')->to('types#delete_type') + $manager_user->get('/types/delete/<:name>')->to('types#delete_type') ->name('delete_type'); $manager_user->post('/types/store_description') ->to('types#post_store_description')->name('update_type_description'); - $manager_user->get('/types/toggle/:name')->to('types#toggle_landing') + $manager_user->get('/types/toggle/<:name>')->to('types#toggle_landing') ->name('toggle_landing_type'); - $manager_user->get('/types/:our_type/map/:bibtex_type') - ->to('types#map_types'); - $manager_user->get('/types/:our_type/unmap/:bibtex_type') + $manager_user->get('/types/<:our_type>/map/<:bibtex_type>') + ->to('types#map_types')->name('map_bibtex_type'); + $manager_user->get('/types/<:our_type>/unmap/<:bibtex_type>') ->to('types#unmap_types')->name('unmap_bibtex_type'); ################ AUTHORS ################ @@ -529,117 +412,109 @@ sub setup_routes { ->name('add_author'); $manager_user->post('/authors/add/')->to('authors#add_post'); - $logged_user->get('/authors/edit/:id')->to('authors#edit_author') + $logged_user->get('/authors/edit/<:id>')->to('authors#edit_author') ->name('edit_author'); $manager_user->post('/authors/edit/')->to('authors#edit_post') ->name('edit_author_post'); - $manager_user->get('/authors/delete/:id')->to('authors#delete_author') + $manager_user->get('/authors/delete/<:id>')->to('authors#delete_author') ->name('delete_author'); - $admin_user->get('/authors/delete/:id/force') - ->to('authors#delete_author_force'); - - # for dev only!! - $admin_user->get('/authors/decimate')->to('authors#delete_invisible_authors'); + $admin_user->get('/authors/delete/<:id>/force') + ->to('authors#delete_author_force')->name('delete_author_force'); $manager_user->post('/authors/edit_membership_dates') ->to('authors#post_edit_membership_dates') ->name('edit_author_membership_dates'); - $manager_user->get('/authors/:id/add_to_team/:tid') + $manager_user->get('/authors/<:id>/add_to_team/<:tid>') ->to('authors#add_to_team')->name('add_author_to_team'); - $manager_user->get('/authors/:id/remove_from_team/:tid') + $manager_user->get('/authors/<:id>/remove_from_team/<:tid>') ->to('authors#remove_from_team')->name('remove_author_from_team'); - $manager_user->get('/authors/:masterid/remove_uid/:uid') + $manager_user->get('/authors/<:master_id>/remove_uid/<:minor_id>') ->to('authors#remove_uid')->name('remove_author_uid'); $manager_user->post('/authors/merge/')->to('authors#merge_authors') ->name('merge_authors'); - $admin_user->get('/authors/fix_masters')->to('authors#fix_masters') - ->name('fix_masters'); - $manager_user->get('/authors/reassign') ->to('authors#reassign_authors_to_entries'); $admin_user->get('/authors/reassign_and_create') ->to('authors#reassign_authors_to_entries_and_create_authors'); - $manager_user->get('/authors/toggle_visibility/:id') + $manager_user->get('/authors/toggle_visibility/<:id>') ->to('authors#toggle_visibility')->name('toggle_author_visibility'); - # $logged_user->get('/authors/toggle_visibility') - # ->to('authors#toggle_visibility'); - ################ TAG TYPES ################ # $logged_user->get('/tags/')->to('tags#index')->name("tags_index"); $logged_user->get('/tagtypes')->to('tagtypes#index')->name('all_tag_types'); $admin_user->get('/tagtypes/add')->to('tagtypes#add')->name('add_tag_type'); $admin_user->post('/tagtypes/add')->to('tagtypes#add_post') ->name('add_tag_type_post'); - $admin_user->get('/tagtypes/delete/:id')->to('tagtypes#delete') + $admin_user->get('/tagtypes/delete/<:id>')->to('tagtypes#delete') ->name('delete_tag_type'); - $manager_user->any('/tagtypes/edit/:id')->to('tagtypes#edit') + $manager_user->any('/tagtypes/edit/<:id>')->to('tagtypes#edit') ->name('edit_tag_type'); ################ TAGS ################ - $logged_user->get('/tags/:type')->to('tags#index', type => 1) + $logged_user->get('/tags/<:type>')->to('tags#index', type => 1) ->name('all_tags'); - $admin_user->get('/tags/add/:type')->to('tags#add', type => 1) + $admin_user->get('/tags/add/<:type>')->to('tags#add', type => 1) ->name('add_tag_get'); - $admin_user->post('/tags/add/:type')->to('tags#add_post', type => 1) + $admin_user->post('/tags/add/<:type>')->to('tags#add_post', type => 1) ->name('add_tag_post'); - $logged_user->get('/tags/authors/:id/:type') + $logged_user->get('/tags/authors/<:id>/<:type>') ->to('tags#get_authors_for_tag', type => 1)->name('get_authors_for_tag'); - $admin_user->get('/tags/delete/:id')->to('tags#delete')->name('delete_tag'); + $admin_user->get('/tags/delete/<:id>')->to('tags#delete')->name('delete_tag'); ### EDIT TAG FORM GOES WITH GET - WTF!?! # FIXME: FIX THIS - $manager_user->get('/tags/edit/:id')->to('tags#edit')->name('edit_tag'); + $manager_user->get('/tags/edit/<:id>')->to('tags#edit')->name('edit_tag'); - $anyone->get('/read/authors-for-tag/:tag_id/:team_id') + $anyone->get('/read/authors-for-tag/<:tag_id>/<:team_id>') ->to('tags#get_authors_for_tag_and_team') ->name('get_authors_for_tag_and_team'); #ALIAS - $anyone->get('/r/a4t/:tag_id/:team_id') + $anyone->get('/r/a4t/<:tag_id>/<:team_id>') ->to('tags#get_authors_for_tag_and_team') ->name('get_authors_for_tag_and_team'); - $anyone->get('/read/authors-for-tag/:tag_id/:team_id') + $anyone->get('/read/authors-for-tag/<:tag_id>/<:team_id>') ->to('tags#get_authors_for_tag_and_team') ->name('get_authors_for_tag_and_team'); #ALIAS - $anyone->get('/r/a4t/:tag_id/:team_id') + $anyone->get('/r/a4t/<:tag_id>/<:team_id>') ->to('tags#get_authors_for_tag_and_team') ->name('get_authors_for_tag_and_team'); - $anyone->get('/read/tags-for-author/:author_id') + $anyone->get('/read/tags-for-author/<:author_id>') ->to('tags#get_tags_for_author_read')->name('tags_for_author'); #ALIAS - $anyone->get('/r/t4a/:author_id')->to('tags#get_tags_for_author_read'); + $anyone->get('/r/t4a/<:author_id>')->to('tags#get_tags_for_author_read'); - $anyone->get('/read/tags-for-team/:team_id') + $anyone->get('/read/tags-for-team/<:team_id>') ->to('tags#get_tags_for_team_read')->name('tags_for_team'); #ALIAS - $anyone->get('/r/t4t/:team_id')->to('tags#get_tags_for_team_read'); + $anyone->get('/r/t4t/<:team_id>')->to('tags#get_tags_for_team_read'); ################ TEAMS ################ $logged_user->get('/teams')->to('teams#show')->name('all_teams'); - $manager_user->get('/teams/edit/:id')->to('teams#edit')->name('edit_team'); - $manager_user->get('/teams/delete/:id')->to('teams#delete_team') + $manager_user->get('/teams/edit/<:id>')->to('teams#edit')->name('edit_team'); + $manager_user->get('/teams/delete/<:id>')->to('teams#delete_team') ->name('delete_team'); - $manager_user->get('/teams/delete/:id/force')->to('teams#delete_team_force') - ->name('delete_team_force'); - $logged_user->get('/teams/unrealted_papers/:teamid') + $manager_user->get('/teams/delete/<:id>/force') + ->to('teams#delete_team_force')->name('delete_team_force'); + $logged_user->get('/teams/<:teamid>/unrelated_papers') ->to('publications#show_unrelated_to_team') ->name('unrelated_papers_for_team'); $manager_user->get('/teams/add')->to('teams#add_team')->name('add_team_get'); - $manager_user->post('/teams/add/')->to('teams#add_team_post'); + $manager_user->post('/teams/add/')->to('teams#add_team_post') + ->name('add_team_post'); ################ EDITING PUBLICATIONS ################ #<<< no perltidy here @@ -664,11 +539,11 @@ sub setup_routes { # ->to('publications#all_ajax') # ->name('publications_ajax'); - $logged_user->get('/publications/recently_added/:num') + $logged_user->get('/publications/recently_added/<:num>') ->to('publications#all_recently_added') ->name('recently_added'); - $logged_user->get('/publications/recently_modified/:num') + $logged_user->get('/publications/recently_modified/<:num>') ->to('publications#all_recently_modified') ->name('recently_changed'); @@ -679,7 +554,7 @@ sub setup_routes { ->to('publications#delete_orphaned')->name('delete_orphaned'); - $logged_user->get('/publications/untagged/:tagtype') + $logged_user->get('/publications/untagged/<:tagtype>') ->to( 'publications#all_without_tag') ->name('get_untagged_publications'); @@ -690,7 +565,7 @@ sub setup_routes { $manager_user->get('/publications/missing_month') ->to('publications#all_with_missing_month'); - $logged_user->get('/publications/get/:id') + $logged_user->get('/publications/get/<:id>') ->to('publications#single') ->name('get_single_publication'); @@ -702,13 +577,10 @@ sub setup_routes { ->to('publications#download') ->name('download_publication_pdf'); - $anyone->get('/publications/download/:filetype/') ->to('publications#download') ->name('download_publication'); - - $manager_user->get('/publications/discover_attachments/') ->to('publications#discover_attachments') ->name('discover_attachments'); @@ -723,7 +595,7 @@ sub setup_routes { ####### ATTACHMENTS END - $manager_user->get('/publications/toggle_hide/:id') + $manager_user->get('/publications/toggle_hide/<:id>') ->to('publications#toggle_hide') ->name('toggle_hide_publication'); @@ -737,69 +609,69 @@ sub setup_routes { ->to('publications#publications_add_post') ->name('add_publication_post'); - $manager_user->get('/publications/edit/:id') + $manager_user->get('/publications/edit/<:id>') ->to('publications#publications_edit_get') ->name('edit_publication'); - $manager_user->post('/publications/edit/:id') + $manager_user->post('/publications/edit/<:id>') ->to('publications#publications_edit_post') ->name('edit_publication_post'); - $manager_user->get('/publications/make_paper/:id') + $manager_user->get('/publications/make_paper/<:id>') ->to('publications#make_paper') ->name('make_paper'); - $manager_user->get('/publications/make_talk/:id') + $manager_user->get('/publications/make_talk/<:id>') ->to('publications#make_talk') ->name('make_talk'); - $manager_user->get('/publications/regenerate/:id') + $manager_user->get('/publications/regenerate/<:id>') ->to('publications#regenerate_html') ->name('regenerate_publication'); # change to POST or DELETE - $manager_user->get('/publications/delete_sure/:id') + $manager_user->get('/publications/delete_sure/<:id>') ->to('publications#delete_sure') ->name('delete_publication_sure'); - $manager_user->get('/publications/attachments/:id') + $manager_user->get('/publications/attachments/<:id>') ->to('publications#add_pdf') ->name('manage_attachments'); - $manager_user->post('/publications/add_pdf/do/:id') + $manager_user->post('/publications/add_pdf/do/<:id>') ->to('publications#add_pdf_post') ->name('post_upload_pdf'); - $manager_user->get('/publications/manage_tags/:id') + $manager_user->get('/publications/manage_tags/<:id>') ->to('publications#manage_tags') ->name('manage_tags'); # change to POST or DELETE - $manager_user->get('/publications/:eid/remove_tag/:tid') + $manager_user->get('/publications/<:eid>/remove_tag/<:tid>') ->to('publications#remove_tag') ->name('remove_tag_from_publication'); # change to POST or UPDATE - $manager_user->get('/publications/:eid/add_tag/:tid') + $manager_user->get('/publications/<:eid>/add_tag/<:tid>') ->to('publications#add_tag') ->name('add_tag_to_publication'); - $manager_user->get('/publications/manage_exceptions/:id') + $manager_user->get('/publications/manage_exceptions/<:id>') ->to('publications#manage_exceptions') ->name('manage_exceptions'); # change to POST or DELETE - $manager_user->get('/publications/:eid/remove_exception/:tid') + $manager_user->get('/publications/<:eid>/remove_exception/<:tid>') ->to('publications#remove_exception') ->name('remove_exception_from_publication'); # change to POST or UPDATE - $manager_user->get('/publications/:eid/add_exception/:tid') + $manager_user->get('/publications/<:eid>/add_exception/<:tid>') ->to('publications#add_exception') ->name('add_exception_to_publication'); - $logged_user->get('/publications/show_authors/:id') + $logged_user->get('/publications/show_authors/<:id>') ->to('publications#show_authors_of_entry') ->name('show_authors_of_entry'); @@ -811,7 +683,7 @@ sub setup_routes { ->to('PublicationsSeo#metalist') ->name("metalist_all_entries"); - $anyone->get('/read/publications/meta/:id') + $anyone->get('/read/publications/meta/<:id>') ->to('PublicationsSeo#meta') ->name("metalist_entry"); @@ -825,10 +697,10 @@ sub setup_routes { $anyone->get('/r/bibtex')->to('publications#all_bibtex'); #ALIAS $anyone->get('/r/b')->to('publications#all_bibtex'); #ALIAS - $anyone->get('/read/publications/get/:id') + $anyone->get('/read/publications/get/<:id>') ->to('publications#single_read') ->name('get_single_publication_read'); - $anyone->get('/r/p/get/:id') + $anyone->get('/r/p/get/<:id>') ->to('publications#single_read'); ######## PublicationsLanding diff --git a/lib/BibSpace/Backend/IBibSpaceBackend.pm b/lib/BibSpace/Backend/IBibSpaceBackend.pm deleted file mode 100644 index 7c18bea..0000000 --- a/lib/BibSpace/Backend/IBibSpaceBackend.pm +++ /dev/null @@ -1,20 +0,0 @@ -package IBibSpaceBackend; -use v5.16; -use Try::Tiny; -use Data::Dumper; -use namespace::autoclean; - -use Moose::Role; - -requires 'all'; -requires 'count'; -requires 'empty'; -requires 'exists'; -requires 'save'; -requires 'update'; -requires 'delete'; -requires 'filter'; -requires 'find'; - -1; - diff --git a/lib/BibSpace/Backend/SmartArray.pm b/lib/BibSpace/Backend/SmartArray.pm deleted file mode 100644 index ef54237..0000000 --- a/lib/BibSpace/Backend/SmartArray.pm +++ /dev/null @@ -1,180 +0,0 @@ -package SmartArray; - -use v5.16; -use Try::Tiny; -use Data::Dumper; -use namespace::autoclean; - -# for benchmarking -use Time::HiRes qw( gettimeofday tv_interval ); - -use Moose; -use Moose::Util::TypeConstraints; - -use BibSpace::Backend::IBibSpaceBackend; -require BibSpace::Model::IEntity; -with 'IBibSpaceBackend'; -use List::Util qw(first); -use List::MoreUtils qw(first_index); -use feature qw( say ); - -use MooseX::Storage; -with Storage(format => 'JSON', 'io' => 'File'); - -=item - This is a in-memory data structure (hash) to hold all objects of BibSpace. - It is build like this: - String "TypeName" => Array of Objects with type TypeName. - It could be improved for performance like this: - String "TypeName" => { Integer UID => Object with type TypeName}. -=cut - -has 'logger' => - (is => 'ro', does => 'ILogger', required => 1, traits => ['DoNotSerialize']); - -has 'data' => ( - traits => ['Hash'], - is => 'ro', - isa => 'HashRef[ArrayRef[BibSpace::Model::IEntity]]', - default => sub { {} }, - handles => { - set => 'set', - get => 'get', - has => 'exists', - defined => 'defined', - keys => 'keys', - - # values => 'values', - num => 'count', - pairs => 'kv', - _clear => 'clear', - }, -); - -sub reset_data { - my $self = shift; - $self->logger->warn("Resetting SmartArray"); - $self->_clear; -} - -sub dump { - my $self = shift; - $self->logger->debug("SmartArray keys: " . join(', ', $self->keys)); -} - -sub _init { - my ($self, $type) = @_; - die "_init requires a type!" unless $type; - if (!$self->defined($type)) { - $self->set($type, []); - } -} - -sub all { - my ($self, $type) = @_; - - $self->logger->error("SmartArray->all requires a type! Type: $type.") - unless $type; - $self->_init($type); - my $aref = $self->get($type); - return @{$aref}; -} - -sub _add { - my ($self, @objects) = @_; - my $type = ref($objects[0]); - $self->_init($type); - push @{$self->get($type)}, @objects; -} - -sub save { - my ($self, @objects) = @_; - my $added = 0; - - # if there are multiple objects to add and the array is empty -> do it quicker! - if (@objects > 0) { - my $type = ref($objects[0]); - - if ($self->empty($type)) { - $self->_add(@objects); - $added = scalar @objects; - return $added; - } - } - - foreach my $obj (@objects) { - if (!$self->exists($obj)) { - ++$added; - $self->_add($obj); - } - else { - $self->update($obj); - } - } - - return $added; -} - -sub count { - my ($self, $type) = @_; - die "all requires a type!" unless $type; - return scalar $self->all($type); -} - -sub empty { - my ($self, $type) = @_; - return $self->count($type) == 0; -} - -## this is mega slow for relations!!! -sub exists { - my ($self, $object) = @_; - my $type = ref($object); - $self->logger->error( - "SmartArray->exists requires a type! Object: '$object', type: '$type'.") - unless $type; - my $found = first { $_->equals($object) } $self->all($type); - return defined $found; -} - -sub update { - my ($self, @objects) = @_; - - # should happen automatically beacuse array keeps references to objects -} - -sub delete { - my ($self, @objects) = @_; - my $type = ref($objects[0]); - my $aref = $self->get($type); - my @removed; - foreach my $obj (@objects) { - my $idx = first_index { $_ == $obj } @{$aref}; - push @removed, splice(@{$aref}, $idx, 1) if $idx > -1; - } - return @removed; -} - -sub filter { - my ($self, $type, $coderef) = @_; - - return () if $self->empty($type); - my @arr = grep &{$coderef}, $self->all($type); - - return @arr; -} - -sub find { - my ($self, $type, $coderef) = @_; - - return if $self->empty($type); - my $obj = first \&{$coderef}, $self->all($type); - - return $obj; -} - -# Moose::Meta::Attribute::Native::Trait::Array - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/Backend/SmartBackendHelper.pm b/lib/BibSpace/Backend/SmartBackendHelper.pm deleted file mode 100644 index b5435a0..0000000 --- a/lib/BibSpace/Backend/SmartBackendHelper.pm +++ /dev/null @@ -1,79 +0,0 @@ -package BibSpace::Backend::SmartBackendHelper; - -use v5.16; -use Try::Tiny; -use namespace::autoclean; - -sub linkData { - my $app = shift; - - $app->logger->info("Linking Authors (N) to (1) Authors."); - foreach - my $author ($app->repo->authors_filter(sub { $_->id != $_->master_id })) - { - my $master = $app->repo->authors_find(sub { $_->id == $author->master_id }); - if ($master and $author) { - $author->set_master($master); - } - } - - $app->logger->info("Linking Authors (N) to (M) Entries."); - foreach my $auth ($app->repo->authorships_all) { - my $entry = $app->repo->entries_find(sub { $_->id == $auth->entry_id }); - my $author = $app->repo->authors_find(sub { $_->id == $auth->author_id }); - if ($entry and $author) { - $auth->entry($entry); - $auth->author($author); - $entry->authorships_add($auth); - $author->authorships_add($auth); - } - } - - $app->app->logger->info("Linking Tags (N) to (M) Entries."); - foreach my $labeling ($app->repo->labelings_all) { - my $entry = $app->repo->entries_find(sub { $_->id == $labeling->entry_id }); - my $tag = $app->repo->tags_find(sub { $_->id == $labeling->tag_id }); - if ($entry and $tag) { - $labeling->entry($entry); - $labeling->tag($tag); - $entry->labelings_add($labeling); - $tag->labelings_add($labeling); - } - } - - $app->app->logger->info("Linking Teams (Exceptions) (N) to (M) Entries."); - foreach my $exception ($app->repo->exceptions_all) { - my $entry - = $app->repo->entries_find(sub { $_->id == $exception->entry_id }); - my $team = $app->repo->teams_find(sub { $_->id == $exception->team_id }); - if ($entry and $team) { - $exception->entry($entry); - $exception->team($team); - $entry->exceptions_add($exception); - $team->exceptions_add($exception); - } - } - - $app->app->logger->info("Linking Teams (N) to (M) Authors."); - foreach my $membership ($app->repo->memberships_all) { - my $author - = $app->repo->authors_find(sub { $_->id == $membership->author_id }); - my $team = $app->repo->teams_find(sub { $_->id == $membership->team_id }); - if (defined $author and defined $team) { - $membership->author($author); - $membership->team($team); - $author->memberships_add($membership); - $team->memberships_add($membership); - } - } - - $app->app->logger->info("Linking TagTypes (N) to (1) Tags."); - foreach my $tag ($app->repo->tags_all) { - my $tagtype = $app->repo->tagTypes_find(sub { $_->id == $tag->type }); - if ($tag and $tagtype) { - $tag->tagtype($tagtype); - } - } -} - -1; diff --git a/lib/BibSpace/Backend/SmartHash.pm b/lib/BibSpace/Backend/SmartHash.pm deleted file mode 100644 index b487723..0000000 --- a/lib/BibSpace/Backend/SmartHash.pm +++ /dev/null @@ -1,173 +0,0 @@ -package SmartHash; - -use v5.16; -use Try::Tiny; -use Data::Dumper; -use namespace::autoclean; - -# for benchmarking -use Time::HiRes qw( gettimeofday tv_interval ); - -use Moose; -use Moose::Util::TypeConstraints; - -use BibSpace::Backend::IBibSpaceBackend; - -require BibSpace::Model::IEntity; -with 'IBibSpaceBackend'; -use List::Util qw(first); -use List::MoreUtils qw(any uniq first_index); -use feature qw( say ); - -=item - This is a in-memory data structure (hash) to hold all objects of BibSpace. - It is build like this: - String "TypeName" => Array of Objects with type TypeName. - It could be improved for performance like this: - String "TypeName" => { Integer UID => Object with type TypeName}. -=cut - -has 'logger' => (is => 'ro', does => 'ILogger', required => 1); - -has 'data' => ( - traits => ['Hash'], - is => 'ro', - isa => 'HashRef[HashRef[BibSpace::Model::IEntity]]', - default => sub { {} }, - handles => { - set => 'set', - get => 'get', - has => 'exists', - defined => 'defined', - keys => 'keys', - - # values => 'values', - num => 'count', - pairs => 'kv', - _clear => 'clear', - }, -); - -sub reset_data { - my $self = shift; - $self->logger->warn("Resetting SmartHash"); - $self->_clear; -} - -sub dump { - my $self = shift; - $self->logger->debug("SmartHash keys: " . join(', ', $self->keys)); -} - -sub _init { - my ($self, $type) = @_; - die "_init requires a type!" unless $type; - if (!$self->defined($type)) { - $self->set($type, {}); - } - -} - -sub all { - my ($self, $type) = @_; - die "all requires a type!" unless $type; - $self->_init($type); - my $href = $self->get($type); - - my @result; - if ($href) { - @result = values %$href; - } - return @result; -} - -sub _add { - my ($self, @objects) = @_; - return if scalar(@objects) == 0; - - my $type = ref($objects[0]); - - $self->_init($type); - my $href = $self->get($type); - - my $num_added = 0; - foreach my $obj (@objects) { - $href->{$obj->id} = $obj; - $num_added++; - } - return $num_added; -} - -sub save { - my ($self, @objects) = @_; - return $self->_add(@objects); -} - -sub count { - my ($self, $type) = @_; - die "all requires a type!" unless $type; - return 0 if $self->empty($type); - - $self->_init($type); - my $href = $self->get($type); - return scalar keys %$href; -} - -sub empty { - my ($self, $type) = @_; - $self->_init($type); - my $href = $self->get($type); - return scalar(keys %$href) == 0; -} - -sub exists { - my ($self, $object) = @_; - my $type = ref($object); - $self->logger->error( - "SmartHash->exists requires a type! Object: '$object', type: '$type'.") - unless $type; - my $href = $self->get($type); - return exists $href->{$object->id}; -} - -sub update { - my ($self, @objects) = @_; - return $self->_add(@objects); -} - -sub delete { - my ($self, @objects) = @_; - my $type = ref($objects[0]); - my $href = $self->get($type); - - my @removed; - foreach my $obj (@objects) { - push @removed, delete $href->{$obj->id}; - } - - return @removed; -} - -sub filter { - my ($self, $type, $coderef) = @_; - - return () if $self->empty($type); - my @arr = grep &{$coderef}, $self->all($type); - - return @arr; -} - -sub find { - my ($self, $type, $coderef) = @_; - - return if $self->empty($type); - my $obj = first \&{$coderef}, $self->all($type); - - return $obj; -} - -# Moose::Meta::Attribute::Native::Trait::Array - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/Controller/Authors.pm b/lib/BibSpace/Controller/Authors.pm index 1720380..79171ac 100644 --- a/lib/BibSpace/Controller/Authors.pm +++ b/lib/BibSpace/Controller/Authors.pm @@ -36,15 +36,17 @@ sub all_authors { # refactored @authors = grep { $_->is_master } @authors; if ($letter) { - @authors = grep { (substr($_->master, 0, 1) cmp $letter) == 0 } @authors; + @authors + = grep { (substr($_->get_master->name, 0, 1) cmp $letter) == 0 } @authors; } my @letters; if (defined $visible) { - @letters = map { substr($_->master, 0, 1) } + @letters = map { substr($_->get_master->name, 0, 1) } $self->app->repo->authors_filter(sub { $_->display == $visible }); } else { - @letters = map { substr($_->master, 0, 1) } $self->app->repo->authors_all; + @letters = map { substr($_->get_master->name, 0, 1) } + $self->app->repo->authors_all; } @letters = uniq @letters; @letters = sort @letters; @@ -75,7 +77,7 @@ sub add_post { if (defined $new_master and length($new_master) > 0) { my $author - = $self->app->repo->authors_find(sub { $_->master eq $new_master }); + = $self->app->repo->authors_find(sub { $_->master->name eq $new_master }); if (!defined $author) { # no such user exists yet @@ -164,9 +166,7 @@ sub add_to_team { my $team = $self->app->repo->teams_find(sub { $_->id == $team_id }); if (defined $author and defined $team) { - my $membership = Membership->new( - author => $author->get_master, - team => $team, + my $membership = $self->app->repo->entityFactory->new_Membership( author_id => $author->get_master->id, team_id => $team->id ); @@ -175,10 +175,7 @@ sub add_to_team { $author->add_membership($membership); $self->flash( - msg => "Author " - . $author->uid - . " has just joined team " - . $team->name . "", + msg => "Author " . $author->uid . " has just joined team " . $team->name, msg_type => "success" ); } @@ -188,7 +185,8 @@ sub add_to_team { msg_type => "danger" ); } - $self->redirect_to($self->get_referrer); + $self->redirect_to($self->url_for('edit_author', id => $author->id) + || $self->get_referrer); } sub remove_from_team { @@ -200,16 +198,18 @@ sub remove_from_team { my $team = $self->app->repo->teams_find(sub { $_->id == $team_id }); if (defined $author and defined $team) { - my $membership = $author->memberships_find(sub { $_->team->equals($team) }); + my $search_membership = $self->app->repo->entityFactory->new_Membership( + author_id => $author->get_master->id, + team_id => $team->id + ); + my $membership = $self->app->repo->memberships_find( + sub { $_->equals($search_membership) }); $author->remove_membership($membership); $team->remove_membership($membership); $self->app->repo->memberships_delete($membership); $self->flash( - msg => "Author " - . $author->uid - . " has just left team " - . $team->name . "", + msg => "Author " . $author->uid . " has just left team " . $team->name, msg_type => "success" ); } @@ -219,13 +219,14 @@ sub remove_from_team { msg_type => "danger" ); } - $self->redirect_to($self->get_referrer); + $self->redirect_to($self->url_for('edit_author', id => $author->id) + || $self->get_referrer); } sub remove_uid { my $self = shift; - my $master_id = $self->param('masterid'); - my $minor_id = $self->param('uid'); + my $master_id = $self->param('master_id'); + my $minor_id = $self->param('minor_id'); my $author_master = $self->app->repo->authors_find(sub { $_->id == $master_id }); @@ -250,7 +251,10 @@ sub remove_uid { my @master_entries = $author_master->get_entries; # remove master authorships from both authors - foreach my $master_authorship ($author_master->authorships_all) { + my @author_authorships + = $self->app->repo->authorships_filter(sub { $_->author_id == $master_id } + ); + foreach my $master_authorship (@author_authorships) { $author_master->remove_authorship($master_authorship); $author_minor->remove_authorship($master_authorship); @@ -259,7 +263,10 @@ sub remove_uid { } # remove minion authorships from both authors - foreach my $minion_authorship ($author_minor->authorships_all) { + my @minion_authorships + = $self->app->repo->authorships_filter(sub { $_->author_id == $minor_id } + ); + foreach my $minion_authorship (@minion_authorships) { $author_minor->remove_authorship($minion_authorship); $author_master->remove_authorship($minion_authorship); @@ -305,27 +312,31 @@ sub merge_authors { if (defined $author_source and defined $author_destination) { if ($author_destination->can_merge_authors($author_source)) { - my @src_authorships = $author_source->authorships_all; + my @src_memberships = $self->app->repo->memberships_filter( + sub { $_->author_id == $author_source->id }); + + my @src_authorships = $self->app->repo->authorships_filter( + sub { $_->author_id == $author_source->id }); + foreach my $src_authorship (@src_authorships) { # Removing the authorship from the source author - $src_authorship->author->remove_authorship($src_authorship); - - # authorships cannot be updated, so we need to delete and add later $self->app->repo->authorships_delete($src_authorship); - # Changing the authorship to point to a new author - $src_authorship->author($author_destination); + # Changing the authorship to point to a new author (new object is required) - # store changes the authorship in the repo - $self->app->repo->authorships_save($src_authorship); + my $new_authorship = $self->app->repo->entityFactory->new_Authorship( + author_id => $author_destination->id, + entry_id => $src_authorship->entry_id + ); - # Adding the authorship to the new author - $author_destination->add_authorship($src_authorship); + # store changes the authorship in the repo + $self->app->repo->authorships_save($new_authorship); } - $author_source->memberships_clear; - $author_source->set_master($author_destination); + # Source author abandons all teams - information about their teams is destroyed + $self->app->repo->authorships_delete(@src_memberships); + $author_source->set_master($author_destination); $self->app->repo->authors_save($author_destination); $self->app->repo->authors_save($author_source); @@ -333,8 +344,14 @@ sub merge_authors { Freassign_authors_to_entries_given_by_array($self->app, 0, \@entries); $self->flash( - msg => - "Author $copy_name was merged into $author_destination->{master}.", + msg => "Author $copy_name was merged into " + . $author_destination->master->name . ". " + . "Information about teams of author $copy_name was deleted. " + . "The new master " + . $author_destination->name + . " does not change their teams. " + . "All entries of author $copy_name are now assigned to author " + . $author_destination->name . ".", msg_type => "success" ); } @@ -353,7 +370,9 @@ sub merge_authors { ); } - $self->redirect_to($self->get_referrer); + $self->redirect_to( + $self->url_for('edit_author', id => $author_destination->id) + || $self->get_referrer); } sub edit_post { @@ -369,10 +388,10 @@ sub edit_post { if (defined $new_master) { my $existing = $self->app->repo->authors_find( - sub { ($_->master cmp $new_master) == 0 }); + sub { ($_->master->name cmp $new_master) == 0 }); if (!defined $existing) { - $author->update_master_name($new_master); + $author->update_name($new_master); $self->app->repo->authors_save($author); $self->flash( msg => "Master name has been updated successfully.", @@ -385,7 +404,7 @@ sub edit_post { $self->flash( msg => "This master name is already taken by url_for('edit_author', id => $existing->id) . "\">" - . $existing->master . ".", + . $existing->master->name . ".", msg_type => "danger" ); $self->redirect_to($self->url_for('edit_author', id => $id)); @@ -404,7 +423,7 @@ sub edit_post { if (defined $existing_author) { $self->flash( msg => - "Cannot add user ID $new_user_id. Such ID already exist. Maybe you wan to merge authors?", + "Cannot add user ID $new_user_id. Such ID already exist. Maybe you want to merge authors instead?", msg_type => "warning" ); } @@ -430,14 +449,12 @@ sub post_edit_membership_dates { my $team = $self->app->repo->teams_find(sub { $_->id == $team_id }); if ($author and $team) { - my $search_mem = Membership->new( - author => $author->get_master, - team => $team, + my $search_membership = $self->app->repo->entityFactory->new_Membership( author_id => $author->get_master->id, team_id => $team->id ); - my $membership - = $self->app->repo->memberships_find(sub { $_->equals($search_mem) }); + my $membership = $self->app->repo->memberships_find( + sub { $_->equals($search_membership) }); if ($membership) { @@ -468,7 +485,7 @@ sub delete_author { my $self = shift; my $id = $self->param('id'); - my $author = $self->app->repo->authors_find(sub { $_->{id} == $id }); + my $author = $self->app->repo->authors_find(sub { $_->id == $id }); if ($author and $author->can_be_deleted()) { $self->delete_author_force(); @@ -489,39 +506,25 @@ sub delete_author_force { if ($author) { - ## TODO: refactor these blocks nicely! - - ## Deleting memberships - my @memberships = $author->memberships_all; - - # for each team, remove membership in this team - foreach my $membership (@memberships) { - $membership->team->remove_membership($membership); - } + # Deleting memberships + my @memberships = $self->app->repo->memberships_filter( + sub { $_->author_id == $author->id }); $self->app->repo->memberships_delete(@memberships); - # remove all memberships for this team - $author->memberships_clear; - - ## Deleting authorships - my @authorships = $author->authorships_all; - - # for each team, remove authorship in this team - foreach my $authorship (@authorships) { - $authorship->entry->remove_authorship($authorship); - } + # Deleting authorships + my @authorships = $self->app->repo->authorships_filter( + sub { $_->author_id == $author->id }); $self->app->repo->authorships_delete(@authorships); - # remove all authorships for this team - $author->authorships_clear; - - # finally delete author + # Finally delete author $self->app->repo->authors_delete($author); $self->app->logger->info( "Author " . $author->uid . " ID $id has been deleted."); $self->flash( - msg => "Author " . $author->uid . " ID $id removed successfully.", + msg => "Author " + . $author->uid + . " ID $id has been removed successfully.", msg_type => "success" ); } @@ -532,54 +535,6 @@ sub delete_author_force { $self->redirect_to($self->url_for('all_authors')); } -## do not use this on production! this is for making the tests faster!! -sub delete_invisible_authors { - my $self = shift; - - my @authors = $self->app->repo->authors_filter(sub { !$_->is_visible }); - - foreach my $author (@authors) { - - ## TODO: refactor these blocks nicely! - - ## Deleting memberships - my @memberships = $author->memberships_all; - - # for each team, remove membership in this team - foreach my $membership (@memberships) { - $membership->team->remove_membership($membership); - } - $self->app->repo->memberships_delete(@memberships); - - # remove all memberships for this team - $author->memberships_clear; - - ## Deleting authorships - my @authorships = $author->authorships_all; - - # for each team, remove authorship in this team - foreach my $authorship (@authorships) { - - # my $entry = $authorship->entry; - $authorship->entry->remove_authorship($authorship); - - # $self->app->repo->entries_delete($entry); - } - $self->app->repo->authorships_delete(@authorships); - - # remove all authorships for this team - $author->authorships_clear; - - # finally delete author - $self->app->repo->authors_delete($author); - - $self->flash(msg => "Authors decimated! ", msg_type => "success"); - } - - $self->redirect_to($self->url_for('all_authors')); - -} - sub reassign_authors_to_entries { my $self = shift; my $create_new = shift // 0; @@ -600,66 +555,6 @@ sub reassign_authors_to_entries_and_create_authors { $self->reassign_authors_to_entries(1); } -sub fix_masters { - my $self = shift; - - my @all_authors = $self->app->repo->authors_all; - - my @broken_authors_0 - = grep { ($_->is_minion) and (!defined $_->masterObj) } @all_authors; - - # masterObj not set although it should be - my @broken_authors_1 - = grep { (!defined $_->masterObj) and ($_->master_id != $_->id) } - @all_authors; - - # masterObj set incorrectly - my @broken_authors_2 - = grep { $_->masterObj and $_->master_id != $_->masterObj->id } - @all_authors; - - my $num_fixes_0 = @broken_authors_0; - my $num_fixes_1 = @broken_authors_1; - my $num_fixes_2 = @broken_authors_2; - - my $msg_type - = ($num_fixes_0 + $num_fixes_1 + $num_fixes_2) == 0 ? 'success' : 'danger'; - my $msg = "Analysis is finished. Authors broken: -
    -
  • " - . scalar(@broken_authors_0) - . " of type 0 (is minion but master undefined)
  • -
  • " - . scalar(@broken_authors_1) - . " of type 1 (masterObj not set although it should)
  • -
  • " - . scalar(@broken_authors_2) . " of type 2 (masterObj set incorrectly)
  • -
"; - - # we cure all problems with the same medicine... - foreach my $author ((@broken_authors_0, @broken_authors_1, @broken_authors_2)) - { - my $master - = $self->app->repo->authors_find(sub { $_->id == $author->master_id }); - if (defined $master) { - $author->masterObj($master); - ++$num_fixes_0; - ++$num_fixes_1; - ++$num_fixes_2; - } - } - $msg - .= "
Fixing is finished. Masters were re-added to the authors. Fixed: -
    -
  • $num_fixes_0 of type 0 (is minion but master undefined)
  • -
  • $num_fixes_1 of type 1 (masterObj not set although it should)
  • -
  • $num_fixes_2 of type 2 (masterObj set incorrectly)
  • -
"; - - $self->flash(msg => $msg, msg_type => $msg_type); - $self->redirect_to($self->get_referrer); -} - sub toggle_visibility { my $self = shift; my $id = $self->param('id'); diff --git a/lib/BibSpace/Controller/Backup.pm b/lib/BibSpace/Controller/Backup.pm index 92c503f..d0a86df 100644 --- a/lib/BibSpace/Controller/Backup.pm +++ b/lib/BibSpace/Controller/Backup.pm @@ -19,9 +19,7 @@ use List::Util qw(first); use BibSpace::Functions::Core; use BibSpace::Functions::MySqlBackupFunctions; use BibSpace::Functions::BackupFunctions; - use BibSpace::Model::Backup; -use Storable; use Mojo::Base 'Mojolicious::Controller'; use Mojo::Base 'Mojolicious::Plugin::Config'; @@ -54,7 +52,7 @@ sub index { sub save { my $self = shift; - return $self->save_storable; + return $self->save_json; } sub save_json { @@ -71,20 +69,6 @@ sub save_json { $self->redirect_to('backup_index'); } -sub save_storable { - my $self = shift; - - my $backup = do_storable_backup($self->app); - - if ($backup->is_healthy) { - $self->flash(msg_type => 'success', msg => "Backup created successfully"); - } - else { - $self->flash(msg_type => 'danger', msg => "Backup create failed!"); - } - $self->redirect_to('backup_index'); -} - sub save_mysql { my $self = shift; @@ -193,10 +177,7 @@ sub restore_backup { my $msg = ''; if ($backup and $backup->is_healthy) { - if ($backup->type eq 'storable') { - return $self->controller_restore_storable_backup($backup); - } - elsif ($backup->type eq 'json') { + if ($backup->type eq 'json') { return $self->controller_restore_json_backup($backup); } else { @@ -244,35 +225,4 @@ sub controller_restore_json_backup { return; } -sub controller_restore_storable_backup { - my $self = shift; - my $backup = shift; - - if ($backup and $backup->is_healthy) { - - restore_storable_backup($backup, $self->app); - - $self->app->logger->info("Restoring storable backup " . $backup->uuid); - - my $status - = "Status:
"
-      . $self->app->repo->lr->get_summary_table
-      . "
"; - - $self->flash( - msg_type => 'success', - msg => - "Backup restored successfully. Database recreated, persistence layers in sync. $status" - ); - } - else { - $self->flash( - msg_type => 'danger', - msg => "Cannot restore - backup not healthy!" - ); - } - $self->redirect_to('backup_index'); - return; -} - 1; diff --git a/lib/BibSpace/Controller/Cron.pm b/lib/BibSpace/Controller/Cron.pm index c74d79e..11a8233 100644 --- a/lib/BibSpace/Controller/Cron.pm +++ b/lib/BibSpace/Controller/Cron.pm @@ -123,7 +123,6 @@ sub cron_run { my $text_to_render; - ############ Cron ACTIONS if ($last_call_hours < $call_freq) { $text_to_render = "Cron level $level called too often. Last call $last_call_hours hours ago. Come back in $left hours\n"; @@ -133,7 +132,6 @@ sub cron_run { $text_to_render = "Cron level $level here\n"; } - ############ Cron ACTIONS $self->app->logger->info("Cron level $level started"); if ($level == 0) { @@ -164,32 +162,29 @@ sub cron_run { sub do_cron_day { my $self = shift; - - my $backup1 = do_storable_backup($self->app, "cron"); - + do_json_backup($self->app, "cron"); + return 1; } sub do_cron_night { - my $self = shift; - + my $self = shift; my @entries = $self->app->repo->entries_all; - for my $e (@entries) { $e->regenerate_html(0, $self->app->bst, $self->app->bibtexConverter); } + return 1; } sub do_cron_week { my $self = shift; - - my $backup1 = do_mysql_backup($self->app, "cron"); - my $num_deleted = delete_old_backups($self->app); - + do_mysql_backup($self->app, "cron"); + delete_old_backups($self->app); + return 1; } sub do_cron_month { my $self = shift; - + return 1; } sub log_cron_usage { @@ -233,27 +228,4 @@ sub get_last_cron_run { return $diff; } -# sub get_last_cron_run_in_hours { -# my $self = shift; -# my $level = shift; - -# my $last_call_str = $self->app->preferences->cron_get($level); -# return 0 if !$last_call_str; - -# my $now = DateTime->now->set_time_zone($self->app->preferences->local_time_zone); -# my $last_call; -# try{ -# $last_call = DateTime::Format::HTTP->parse_datetime( $last_call_str ); -# } -# catch{ -# warn; -# $self->app->logger->error("Cannot parse date of last cron usage. Parser got input: '$last_call_str', error: $_ "); -# }; -# return 0 if !$last_call; - -# my $diff = $now->subtract_datetime($last_call); -# my $hours = $diff->hours; -# return $hours; -# } - 1; diff --git a/lib/BibSpace/Controller/Helpers.pm b/lib/BibSpace/Controller/Helpers.pm index e919c76..5359a97 100644 --- a/lib/BibSpace/Controller/Helpers.pm +++ b/lib/BibSpace/Controller/Helpers.pm @@ -26,25 +26,14 @@ sub register { my ($self, $app) = @_; -# this must be a helper, -# because smartIDprovider can be exchanged during system lifetime (e.g. restore backup), -# so the reference must always point to the currently valid id provider -# smartIDProvider must be instantiated INSIDE LayeredRepository - $app->helper( - smartIDProvider => sub { - my $self = shift; - return $self->app->layeredRepository->uidProvider; - } - ); - # this must be a helper, # because entityFactory can be exchanged during system lifetime (e.g. restore backup), # so the reference must always point to the currently valid id provider -# entityFactory must be instantiated INSIDE LayeredRepository +# entityFactory must be instantiated INSIDE flatRepository $app->helper( entityFactory => sub { my $self = shift; - return $self->app->layeredRepository->e_factory; + return $self->app->flatRepository->e_factory; } ); @@ -262,7 +251,7 @@ sub register { my $type = shift // 1; my $paper = $self->app->repo->entries_find(sub { $_->id == $eid }); - my @tags = $paper->get_tags($type); + my @tags = $paper->get_tags_of_type($type); @tags = sort { $a->name cmp $b->name } @tags; return @tags; } @@ -275,7 +264,7 @@ sub register { my $type = shift // 1; my $paper = $self->app->repo->entries_find(sub { $_->id == $eid }); - my %has_tags = map { $_ => 1 } $paper->get_tags($type); + my %has_tags = map { $_ => 1 } $paper->get_tags_of_type($type); my @all_tags = $self->app->repo->tags_filter(sub { $_->type == $type }); my @unassigned = grep { not $has_tags{$_} } @all_tags; @unassigned = sort { $a->name cmp $b->name } @unassigned; @@ -287,9 +276,6 @@ sub register { num_authors => sub { my $self = shift; return $self->app->repo->authors_count; - - # return $self->storage->authors_all; - } ); @@ -328,9 +314,7 @@ sub register { my $author = shift; my $tag = shift; - return - scalar $author->authorships_filter( - sub { defined $_ and defined $_->entry and $_->entry->has_tag($tag) }); + return scalar grep { $_->has_tag($tag) } $author->get_entries; } ); @@ -338,7 +322,7 @@ sub register { num_pubs_for_tag => sub { my $self = shift; my $tag = shift; - return $tag->labelings_count // 0; + return scalar $tag->get_entries // 0; } ); diff --git a/lib/BibSpace/Controller/Login.pm b/lib/BibSpace/Controller/Login.pm index b458376..cdafed0 100644 --- a/lib/BibSpace/Controller/Login.pm +++ b/lib/BibSpace/Controller/Login.pm @@ -200,11 +200,6 @@ sub profile { $self->render(template => 'login/profile'); } -sub index { - my $self = shift; - $self->render(template => 'login/index'); -} - sub forgot { my $self = shift; $self->app->logger->info("Forgot password form opened"); @@ -238,49 +233,46 @@ sub post_gen_forgot_token { msg_type => 'warning', msg => "User '$login' or email '$email' does not exist. Try again." ); - $self->redirect_to('forgot'); + $self->redirect_to($self->url_for('forgot_password')); return; } - else { - # store token in the user object - $user->forgot_token(generate_token); - - my $email_content = $self->render_to_string('email_forgot_password', - token => $user->forgot_token); - try { - my %email_config = ( - mailgun_domain => $self->app->config->{mailgun_domain}, - mailgun_key => $self->app->config->{mailgun_key}, - from => $self->app->config->{mailgun_from}, - to => $user->email, - content => $email_content, - subject => 'BibSpace password reset request' - ); - send_email(\%email_config); - } - catch { - $self->app->logger->warn( - "Could not sent Email with Mailgun. This is okay for test, but not for production. Error: $_ ." - ); - }; - $self->app->logger->info("Forgot-password-token '" - . $user->forgot_token - . "' sent to '" - . $user->email - . "'."); + # store token in the user object + $user->set_forgot_pass_token(generate_token); + $self->app->repo->users_update($user); - $self->flash( - msg_type => 'info', - msg => - "Email with password reset instructions has been sent. Expect an email from " - . $self->app->config->{mailgun_from} + my $email_content = $self->render_to_string('email_forgot_password', + token => $user->get_forgot_pass_token); + try { + my %email_config = ( + mailgun_domain => $self->app->config->{mailgun_domain}, + mailgun_key => $self->app->config->{mailgun_key}, + from => $self->app->config->{mailgun_from}, + to => $user->email, + content => $email_content, + subject => 'BibSpace password reset request' ); - $self->redirect_to('/'); - + send_email(\%email_config); } + catch { + $self->app->logger->warn("Could not sent Email with Mailgun. Error: $_ ."); + }; + + $self->app->logger->info("Forgot-password-token '" + . $user->get_forgot_pass_token + . "' sent to '" + . $user->email + . "'."); + + $self->flash( + msg_type => 'info', + msg => + "Email with password reset instructions has been sent. Expect an email from " + . $self->app->config->{mailgun_from} + ); + $self->redirect_to('start'); + return; - $self->redirect_to('forgot'); } sub token_clicked { @@ -302,7 +294,11 @@ sub store_password { my $user; if ($token) { $user = $self->app->repo->users_find( - sub { defined $_->forgot_token and $_->forgot_token eq $token }); + sub { + defined $_->get_forgot_pass_token + and $_->get_forgot_pass_token eq $token; + } + ); } if (!$user) { @@ -323,7 +319,8 @@ sub store_password { my $password_hash = encrypt_password($pass1, $salt); $user->pass($password_hash); $user->pass2($salt); - $user->forgot_token(""); + $user->reset_forgot_token; + $self->app->repo->users_update($user); $self->flash( msg_type => 'success', msg => @@ -377,7 +374,7 @@ sub login { $user->record_logging_in; $self->app->logger->info("Login as '$input_login' success."); - $self->redirect_to('/'); + $self->redirect_to('start'); return; } else { @@ -460,7 +457,7 @@ sub register { return; } else { - $self->redirect_to('/noregister'); + $self->redirect_to('registration_disabled'); } } @@ -474,7 +471,7 @@ sub post_do_register { my $password2 = $self->param('password2'); if (!$self->can_register) { - $self->redirect_to('/noregister'); + $self->redirect_to('registration_disabled'); return; } @@ -504,7 +501,7 @@ sub post_do_register { msg_type => 'success', msg => "User created successfully! You may now login using login: $login." ); - $self->redirect_to('/'); + $self->redirect_to('start'); } catch { $failure_reason = $_; diff --git a/lib/BibSpace/Controller/Persistence.pm b/lib/BibSpace/Controller/Persistence.pm index 3f116e3..a5d67f0 100644 --- a/lib/BibSpace/Controller/Persistence.pm +++ b/lib/BibSpace/Controller/Persistence.pm @@ -11,25 +11,13 @@ use Try::Tiny; use Data::Dumper; use BibSpace::Functions::MySqlBackupFunctions; +use BibSpace::Functions::BackupFunctions qw/restore_json_backup/; use BibSpace::Functions::Core; use BibSpace::Model::Backup; -use BibSpace::Functions::BackupFunctions qw(restore_storable_backup); use BibSpace::Functions::FDB; use Mojo::Base 'Mojolicious::Controller'; -sub persistence_status { - my $self = shift; - - my $status - = "Status:
"
-    . $self->app->repo->lr->get_summary_table
-    . "
"; - $self->stash(msg_type => 'success', msg => $status); - $self->flash(msg_type => 'success', msg => $status); - $self->redirect_to($self->get_referrer); -} - sub persistence_status_ajax { my $self = shift; @@ -38,13 +26,13 @@ sub persistence_status_ajax { . $self->app->repo->lr->get_summary_table . ""; $self->render(text => $status); - } sub load_fixture { my $self = shift; - my $fixture_file = $self->app->home->rel_file('fixture/bibspace_fixture.dat'); + my $fixture_file + = $self->app->home->rel_file('fixture/bibspace_fixture.json'); $self->app->logger->info("Loading fixture from: " . $fixture_file->to_string); my $fixture = Backup->new( @@ -52,7 +40,7 @@ sub load_fixture { filename => '' . $fixture_file->basename ); - restore_storable_backup($fixture, $self->app); + restore_json_backup($fixture, $self->app); my $status = "Status:
"
@@ -70,20 +58,20 @@ sub save_fixture {
 
   $self->app->logger->warn("PERSISTENCE CONTROLLER does: save_fixture");
 
-  my $fixture_file = $self->app->home->rel_file('fixture/bibspace_fixture.dat');
+  my $fixture_file
+    = $self->app->home->rel_file('fixture/bibspace_fixture.json');
 
-  my $backup = Backup->create('dummy', "storable");
+  my $backup = Backup->create('dummy', "json");
   $backup->dir('' . $fixture_file->dirname);
   $backup->filename('' . $fixture_file->basename);
 
   my $layer = $self->app->repo->lr->get_read_layer;
   my $path  = "" . $backup->get_path;
 
-  $Storable::forgive_me = "do store regexp please, we will not use them anyway";
-
-# if you see any exceptions being thrown here, this might be due to REGEXP caused by DateTime pattern.
-# this should not happen currently however - I think it is fixed now.
-  Storable::store $layer, $path;
+  # Doing backup
+  my $dtoObject  = BibSpaceDTO->fromLayeredRepo($self->app->repo);
+  my $jsonString = $dtoObject->toJSON;
+  Path::Tiny::path($backup->get_path)->spew($jsonString);
 
   my $status
     = "Status: 
"
@@ -96,141 +84,6 @@ sub save_fixture {
   $self->redirect_to($self->get_referrer);
 }
 
-sub copy_mysql_to_smart {
-  my $self = shift;
-
-  $self->app->logger->warn("PERSISTENCE CONTROLLER does: copy_mysql_to_smart");
-
-  $self->app->repo->lr->copy_data({from => 'mysql', to => 'smart'});
-  $self->app->link_data;
-
-  my $status
-    = "Status: 
"
-    . $self->app->repo->lr->get_summary_table
-    . "
"; - $self->flash(msg_type => 'success', msg => "Copied mysql => smart. $status"); - $self->redirect_to($self->get_referrer); -} - -sub copy_smart_to_mysql { - my $self = shift; - - $self->app->repo->lr->copy_data({from => 'smart', to => 'mysql'}); - - my $status - = "Status:
"
-    . $self->app->repo->lr->get_summary_table
-    . "
"; - $self->flash(msg_type => 'success', msg => "Copied smart => mysql. $status"); - $self->redirect_to($self->get_referrer); -} - -sub insert_random_data { - my $self = shift; - my $num = $self->param('num') // 300; - - my $str_len = 60; - - for (1 .. $num) { - my $obj = $self->app->entityFactory->new_User( - login => random_string($str_len), - email => random_string($str_len) . '@example.com', - real_name => random_string($str_len), - pass => random_string($str_len), - pass2 => random_string($str_len) - - ); - $self->app->repo->users_save($obj); - - $obj - = $self->app->entityFactory->new_Author(uid => random_string($str_len),); - $self->app->repo->authors_save($obj); - - $obj - = $self->app->entityFactory->new_Entry(bib => random_string($str_len),); - $self->app->repo->entries_save($obj); - - $obj - = $self->app->entityFactory->new_TagType(name => random_string($str_len), - ); - $self->app->repo->tagTypes_save($obj); - - my $tt = ($self->app->repo->tagTypes_all)[0]; - - $obj = $self->app->entityFactory->new_Tag( - name => random_string($str_len), - type => $tt->id - ); - $self->app->repo->tags_save($obj); - - $obj - = $self->app->entityFactory->new_Team(name => random_string($str_len),); - $self->app->repo->teams_save($obj); - } - - my $status - = "Status:
"
-    . $self->app->repo->lr->get_summary_table
-    . "
"; - $self->flash(msg_type => 'success', msg => "Copied smart => mysql. $status"); - $self->redirect_to($self->get_referrer); -} - -sub reset_smart { - my $self = shift; - - $self->app->logger->warn("PERSISTENCE CONTROLLER does: reset_smart"); - - my $layer = $self->app->repo->lr->get_layer('smart'); - if ($layer) { - $layer->reset_data; - } - - # no pub_admin user would lock the whole system - # if you insert it here, it may will cause clash of IDs - # $self->app->insert_admin; - # instead, do not insert admin and set system in demo mode - $self->app->preferences->run_in_demo_mode(1); - - say "setting preferences->run_in_demo_mode to: '" - . $self->app->preferences->run_in_demo_mode . "'"; - - my $status - = "Status:
"
-    . $self->app->repo->lr->get_summary_table
-    . "
"; - $self->flash(msg_type => 'success', msg => $status); - $self->redirect_to($self->get_referrer); -} - -sub reset_mysql { - my $self = shift; - - $self->app->logger->warn("PERSISTENCE CONTROLLER does: reset_mysql"); - - my $layer = $self->app->repo->lr->get_layer('mysql'); - if ($layer) { - $layer->reset_data; - my $status - = "Status:
"
-      . $self->app->repo->lr->get_summary_table
-      . "
"; - $self->flash(msg_type => 'success', msg => $status); - } - else { - my $status - = "Status:
"
-      . $self->app->repo->lr->get_summary_table
-      . "
"; - $self->flash( - msg_type => 'danger', - msg => "Reset failed - backend handle undefined. " . $status - ); - } - - $self->redirect_to($self->get_referrer); -} - sub reset_all { my $self = shift; @@ -238,7 +91,6 @@ sub reset_all { my @layers = $self->app->repo->lr->get_all_layers; foreach (@layers) { $_->reset_data } - $self->app->repo->lr->reset_uid_providers; # no pub_admin user would lock the whole system # if you insert it here, it may will cause clash of IDs diff --git a/lib/BibSpace/Controller/Preferences.pm b/lib/BibSpace/Controller/Preferences.pm index b9547d6..5ab925c 100644 --- a/lib/BibSpace/Controller/Preferences.pm +++ b/lib/BibSpace/Controller/Preferences.pm @@ -11,7 +11,6 @@ use Try::Tiny; use Data::Dumper; use Mojo::Base 'Mojolicious::Controller'; -use Storable; use BibSpace::Functions::Core; use BibSpace::Util::Preferences; diff --git a/lib/BibSpace/Controller/Publications.pm b/lib/BibSpace/Controller/Publications.pm index df855f3..4655081 100644 --- a/lib/BibSpace/Controller/Publications.pm +++ b/lib/BibSpace/Controller/Publications.pm @@ -112,7 +112,8 @@ sub all_without_tag { # this will filter entries based on query my @all = Fget_publications_main_hashed_args($self, {year => undef}); - my @untagged_entries = grep { scalar $_->get_tags($tagtype) == 0 } @all; + my @untagged_entries + = grep { scalar $_->get_tags_of_type($tagtype) == 0 } @all; my @filtered = Fget_publications_main_hashed_args($self, {}, \@untagged_entries); @@ -362,11 +363,11 @@ sub delete_orphaned { = $self->app->repo->entries_filter(sub { scalar($_->get_authors) == 0 }); foreach my $entry (@entries) { - my @au = $entry->authorships_all; + my @au = $entry->get_authorships; $self->app->repo->authorships_delete(@au); - my @ex = $entry->exceptions_all; + my @ex = $entry->get_exceptions; $self->app->repo->exceptions_delete(@ex); - my @la = $entry->labelings_all; + my @la = $entry->get_labelings; $self->app->repo->labelings_delete(@la); } @@ -386,7 +387,7 @@ sub fix_file_urls { my @all_entries; - if ($id) { + if ($id and $id > 0) { my $entry = $self->app->repo->entries_find(sub { $_->id == $id }); push @all_entries, $entry if $entry; } @@ -402,17 +403,15 @@ sub fix_file_urls { ++$num_checks; my $str; - $str .= "Entry " . $entry->id . ": "; $entry->discover_attachments($self->app->get_upload_dir); my @discovered_types = $entry->attachments_keys; - $str .= "has types: ("; - foreach (@discovered_types) { - $str .= " $_, "; - } - $str .= "). Fixed: "; + $str .= "Entry " . $entry->id . ": "; + $str + .= "with types: (" + . join(" ", @discovered_types) + . "). Fixed the following: "; - # say $str; my $fixed; my $file = $entry->get_attachment('paper'); my $file_url = $self->url_for( @@ -426,7 +425,7 @@ sub fix_file_urls { $str .= "\n\t"; $entry->add_bibtex_field("pdf", "$file_url"); $fixed = 1; - $str .= "Added Bibtex filed PDF " . $file_url; + $str .= "Added Bibtex field 'pdf = { " . $file_url . "}'"; } $file = $entry->get_attachment('slides'); @@ -441,7 +440,7 @@ sub fix_file_urls { $str .= "\n\t"; $entry->add_bibtex_field("slides", "$file_url"); $fixed = 1; - $str .= "Added Bibtex filed SLIDES " . $file_url; + $str .= "Added Bibtex field 'slides = { " . $file_url . "}'"; } $str .= "\n"; @@ -845,9 +844,9 @@ sub delete_sure { } $entry->delete_all_attachments; - my @entry_authorships = $entry->authorships_all; - my @entry_labelings = $entry->labelings_all; - my @entry_exceptions = $entry->exceptions_all; + my @entry_authorships = $entry->get_authorships; + my @entry_labelings = $entry->get_labelings; + my @entry_exceptions = $entry->get_exceptions; $self->app->repo->authorships_delete(@entry_authorships); $self->app->repo->labelings_delete(@entry_labelings); $self->app->repo->exceptions_delete(@entry_exceptions); @@ -870,7 +869,7 @@ sub show_authors_of_entry { return; } - my @authors = map { $_->author } $entry->authorships_all; + my @authors = $entry->get_authors; my @teams = $entry->get_teams; $self->stash(entry => $entry, authors => \@authors, teams => \@teams); @@ -908,9 +907,7 @@ sub remove_tag { if (defined $entry and defined $tag) { - my $search_label = Labeling->new( - entry => $entry, - tag => $tag, + my $search_label = $self->app->repo->entityFactory->new_Labeling( entry_id => $entry->id, tag_id => $tag->id ); @@ -952,9 +949,7 @@ sub add_tag { my $tag = $self->app->repo->tags_find(sub { $_->id == $tag_id }); if (defined $entry and defined $tag) { - my $label = Labeling->new( - entry => $entry, - tag => $tag, + my $label = $self->app->repo->entityFactory->new_Labeling( entry_id => $entry->id, tag_id => $tag->id ); @@ -986,7 +981,7 @@ sub manage_exceptions { return; } - my @exceptions = $entry->exceptions_all; + my @exceptions = $entry->get_exceptions; my @all_teams = $self->app->repo->teams_all; my @teams = $entry->get_teams; my @authors = $entry->get_authors; @@ -1017,7 +1012,7 @@ sub add_exception { if (defined $entry and defined $team) { - my $exception = Exception->new( + my $exception = $self->app->repo->entityFactory->new_Exception( entry => $entry, team => $team, entry_id => $entry->id, @@ -1060,11 +1055,9 @@ sub remove_exception { if (defined $entry and defined $team) { - my $ex = Exception->new( + my $ex = $self->app->repo->entityFactory->new_Exception( team_id => $team_id, entry_id => $entry_id, - team => $team, - entry => $entry ); my $exception = $self->app->repo->exceptions_find(sub { $_->equals($ex) }); @@ -1228,6 +1221,7 @@ sub publications_add_post { # any action my $existing_entry = $self->app->repo->entries_find( sub { $_->bibtex_key eq $entry->bibtex_key }); + if ($existing_entry) { $status_code_str = 'KEY_TAKEN'; my $msg_type = 'danger'; @@ -1269,7 +1263,7 @@ sub publications_add_post { $added_under_id = $entry->id; ## !!! the entry must be added before executing Freassign_authors_to_entries_given_by_array - ## why? beacuse authorship will be unable to map existing entry to the author + ## why? because authorship will be unable to map existing entry to the author Freassign_authors_to_entries_given_by_array($self->app, 1, [$entry]); my $msg_type = 'success'; diff --git a/lib/BibSpace/Controller/PublicationsExperimental.pm b/lib/BibSpace/Controller/PublicationsExperimental.pm index 02244ef..53cbab9 100644 --- a/lib/BibSpace/Controller/PublicationsExperimental.pm +++ b/lib/BibSpace/Controller/PublicationsExperimental.pm @@ -4,7 +4,6 @@ use Data::Dumper; use utf8; use Text::BibTeX; # parsing bib files use DateTime; -use Path::Tiny; # for creating directories use Try::Tiny; use v5.16; #because of ~~ diff --git a/lib/BibSpace/Controller/Tags.pm b/lib/BibSpace/Controller/Tags.pm index 04cbd4f..1d774da 100644 --- a/lib/BibSpace/Controller/Tags.pm +++ b/lib/BibSpace/Controller/Tags.pm @@ -191,7 +191,7 @@ sub get_tags_for_author_read { my $count = scalar @objs; my $url = $self->url_for('lyp')->query( - author => $author->{master}, + author => $author->master->name, tag => $tag_name, title => '1', navbar => '1' @@ -241,8 +241,10 @@ sub get_tags_for_team_read { foreach my $paper (@team_entries) { # merge two hashes - %team_tags_hash = (%team_tags_hash, - map { "" . $_->name => $_ } $paper->get_tags($tag_type)); + %team_tags_hash = ( + %team_tags_hash, + map { "" . $_->name => $_ } $paper->get_tags_of_type($tag_type) + ); } my @team_tags = values %team_tags_hash; @@ -300,10 +302,10 @@ sub get_authors_for_tag { return; } - my @papers = map { $_->entry } $tag->labelings_all; + my @papers = map { $_->entry } $tag->get_labelings; my @authors; foreach my $paper (@papers) { - my @subset_authors = map { $_->author } $paper->authorships_all; + my @subset_authors = map { $_->author } $paper->get_authorships; push @subset_authors, @authors; } if (!@authors) { @@ -326,20 +328,8 @@ sub delete { if ($tag) { my $name = $tag->name; - ## TODO: refactor these blocks nicely! - ## Deleting labelings - my @labelings = $tag->labelings_all; - - # for each entry, remove labeling in this team - foreach my $labeling (@labelings) { - $labeling->entry->remove_labeling($labeling); - } + my @labelings = $tag->get_labelings; $self->app->repo->labelings_delete(@labelings); - - # remove all labelings for this team - $tag->labelings_clear; - - # finally delete tag $self->app->repo->tags_delete($tag); $self->flash(msg_type => 'success', msg => "Tag $name has been deleted."); diff --git a/lib/BibSpace/Controller/Teams.pm b/lib/BibSpace/Controller/Teams.pm index b1e761d..6ce3ec4 100644 --- a/lib/BibSpace/Controller/Teams.pm +++ b/lib/BibSpace/Controller/Teams.pm @@ -121,7 +121,7 @@ sub do_delete_team { my $team = shift; ## Deleting memberships - my @memberships = $team->memberships_all; + my @memberships = $team->get_memberships; # for each team, remove membership in this team foreach my $membership (@memberships) { diff --git a/lib/BibSpace/Controller/Types.pm b/lib/BibSpace/Controller/Types.pm index 0347f10..446346e 100644 --- a/lib/BibSpace/Controller/Types.pm +++ b/lib/BibSpace/Controller/Types.pm @@ -13,9 +13,6 @@ use Mojo::Base 'Mojolicious::Controller'; use Mojo::Base 'Mojolicious::Plugin::Config'; use Mojo::Log; -# ALTER TABLE OurType_to_Type ADD COLUMN description TEXT DEFAULT NULL; -# ALTER TABLE OurType_to_Type ADD COLUMN landing INTEGER DEFAULT 0; - sub all_our { my $self = shift; @@ -36,8 +33,15 @@ sub post_add_type { my $new_type = $self->param('new_type'); my $type = $self->app->entityFactory->new_Type(our_type => $new_type); - $self->app->repo->types_save($type); + # Databse requires that each type must have at least one bibtex_type + $type->bibtexTypes_add('dummy'); + $self->app->repo->types_save($type); + $self->flash( + msg_type => 'warning', + message => + "Type $new_type has been added and mapped onto dummy bibtex type." + ); $self->redirect_to($self->url_for('all_types')); } @@ -94,7 +98,7 @@ sub post_store_description { $type_obj->description($description); $self->app->repo->types_update($type_obj); } - $self->redirect_to($self->get_referrer); + $self->redirect_to($self->url_for('edit_type', name => $type_name)); } sub delete_type { @@ -118,7 +122,7 @@ sub delete_type { "$type_name cannot be deleted. Possible reasons: mappings exist or it is native bibtex type." ); } - $self->redirect_to($self->get_referrer); + $self->redirect_to($self->url_for('all_types')); } sub map_types { @@ -158,7 +162,7 @@ sub map_types { msg_type => 'danger' ); } - $self->redirect_to($self->get_referrer); + $self->redirect_to($self->url_for('edit_type', name => $o_type)); } sub unmap_types { @@ -197,8 +201,7 @@ sub unmap_types { msg_type => 'danger' ); } - $self->redirect_to($self->get_referrer); - + $self->redirect_to($self->url_for('edit_type', name => $o_type)); } 1; diff --git a/lib/BibSpace/Converter/BibStyleConverter.pm b/lib/BibSpace/Converter/BibStyleConverter.pm index 0353339..fdc9bb8 100644 --- a/lib/BibSpace/Converter/BibStyleConverter.pm +++ b/lib/BibSpace/Converter/BibStyleConverter.pm @@ -403,7 +403,7 @@ sub delatexify { =item string_replace_with_counting uses counting to do strin replace - Example: + Example: single runn of 'string_replace_with_counting' with parameters s = aaa{bbb{cc{dd}}} opening = { @@ -413,11 +413,11 @@ sub delatexify { opening_replace = '' closing replace = '' returns: aaa{bbb{ccdd}} - next run: + next run: returns: aaa{bbbccdd} - next run: + next run: returns: aaabbbccdd -=cut +=cut sub string_replace_with_counting { my ($s, $opening, $closing, $avoid_l, $avoid_r, $opening_replace, diff --git a/lib/BibSpace/DAO/DAOFactory.pm b/lib/BibSpace/DAO/DAOFactory.pm index 99b3271..1f016a9 100644 --- a/lib/BibSpace/DAO/DAOFactory.pm +++ b/lib/BibSpace/DAO/DAOFactory.pm @@ -6,8 +6,6 @@ use namespace::autoclean; use Moose; use BibSpace::Util::ILogger; use BibSpace::DAO::MySQLDAOFactory; -use BibSpace::DAO::RedisDAOFactory; -use BibSpace::DAO::SmartArrayDAOFactory; use BibSpace::Util::EntityFactory; diff --git a/lib/BibSpace/DAO/Interface/IDAO.pm b/lib/BibSpace/DAO/Interface/IDAO.pm index f9160f9..2527f8a 100644 --- a/lib/BibSpace/DAO/Interface/IDAO.pm +++ b/lib/BibSpace/DAO/Interface/IDAO.pm @@ -29,7 +29,6 @@ has 'logger' => (is => 'ro', does => 'ILogger', required => 1); # e.g. database connection handle has 'handle' => (is => 'ro', required => 1, traits => ['DoNotSerialize']); - has 'e_factory' => (is => 'ro', isa => 'EntityFactory', required => 1); 1; diff --git a/lib/BibSpace/DAO/MySQL/AuthorMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/AuthorMySQLDAO.pm index 76d85f2..3817acb 100644 --- a/lib/BibSpace/DAO/MySQL/AuthorMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/AuthorMySQLDAO.pm @@ -22,16 +22,15 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; my $dbh = $self->handle; - my $qry = "SELECT + my $qry = "SELECT id, uid, display, - master, master_id FROM Author"; my @objs; @@ -44,17 +43,9 @@ sub all { id => $row->{id}, uid => $row->{uid}, display => $row->{display}, - master => $row->{master}, master_id => $row->{master_id} ); - # if( $obj->{master_id} != $obj->{id} ){ - # $obj->{masterObj} = MAuthor->static_get($dbh, $obj->{master_id}); - # } - # else{ - # $obj->{masterObj} = $obj; - # } - # FIXME: Temporary fix. This should be fixed with a join! $obj->{masterObj} = undef; $obj->id; # due to lazy filling of this field @@ -69,7 +60,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -86,7 +77,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -94,8 +85,8 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Author LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -103,7 +94,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -111,9 +102,9 @@ sub exists { my $dbh = $self->handle; my $sth = $dbh->prepare( "SELECT EXISTS(SELECT 1 FROM Author WHERE id=? LIMIT 1) as num "); - $sth->execute($object->id); - my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; + my $result = $sth->execute($object->id); + my $row = $sth->fetchrow_hashref(); + my $num = $row->{num} // 0; return $num > 0; } @@ -123,7 +114,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -150,7 +141,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -159,62 +150,71 @@ sub _insert { INSERT INTO Author( id, uid, - master_id, - master, - display - ) - VALUES (?,?,?,?,?);"; + display, + master_id + ) + VALUES (?,?,?,?);"; my $sth = $dbh->prepare($qry); my $added = 0; foreach my $obj (@objects) { + my $id = undef; + my $master_id = undef; + $id = $obj->id if defined $obj->id and $obj->id > 0; + $master_id = $obj->get_master_id + if defined $obj->get_master_id and $obj->get_master_id > 0; try { - my $result - = $sth->execute($obj->id, $obj->uid, $obj->master_id, $obj->master, - $obj->display); - ++$added; + $added += $sth->execute($id, $obj->uid, $obj->display, $master_id); + my $inserted_id = $sth->{mysql_insertid}; + $obj->id($inserted_id); + if (not $master_id) { + + # sets master_id if unset + $obj->get_master_id; + + # This updates master_id to point to i + $obj->repo->authors_update($obj); + } } catch { - $self->logger->error("Insert exception: $_"); + $self->logger->error("Author insert exception: $_"); }; } return $added; - - # $dbh->commit(); } -before '_insert' => sub { shift->logger->entering(""); }; -after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $success = 0; foreach my $obj (@objects) { next if !defined $obj->id; - # update field 'modified_time' only if needed my $qry = "UPDATE Author SET uid=?, master_id=?, - master=?, display=?"; $qry .= " WHERE id = ?"; my $sth = $dbh->prepare($qry); + my $result; try { - my $result = $sth->execute( - $obj->{uid}, $obj->{master_id}, $obj->{master}, - $obj->{display}, $obj->{id} - ); + $sth->execute($obj->uid, $obj->get_master_id, $obj->display, $obj->id); + $success = 1; } catch { + $success = 0; $self->logger->error("Update exception: $_"); }; } + + # Return for tests to signal that nothing has been thrown + return $success; } before 'update' => sub { shift->logger->entering(""); }; after 'update' => sub { shift->logger->exiting(""); }; @@ -222,29 +222,32 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Author WHERE id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->id); + $result = $sth->execute($obj->id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } - + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -259,7 +262,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/AuthorshipMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/AuthorshipMySQLDAO.pm index 29684f2..6fc3c33 100644 --- a/lib/BibSpace/DAO/MySQL/AuthorshipMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/AuthorshipMySQLDAO.pm @@ -22,7 +22,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -36,7 +36,7 @@ sub all { my @objects; while (my $row = $sth->fetchrow_hashref()) { - my $authorship = Authorship->new( + my $authorship = $self->e_factory->new_Authorship( author_id => $row->{author_id}, entry_id => $row->{entry_id} ); @@ -50,7 +50,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -68,7 +68,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -76,8 +76,8 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Entry_to_Author LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -85,7 +85,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -105,7 +105,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -132,7 +132,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -174,7 +174,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -194,28 +194,32 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Entry_to_Author WHERE entry_id=? AND author_id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->entry_id, $obj->author_id); + $result = $sth->execute($obj->entry_id, $obj->author_id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -230,7 +234,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/EntryMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/EntryMySQLDAO.pm index 5676087..a27b661 100644 --- a/lib/BibSpace/DAO/MySQL/EntryMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/EntryMySQLDAO.pm @@ -22,7 +22,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -66,28 +66,16 @@ sub all { my $mt = $mysqlPattern->parse_datetime($row->{modified_time}); # set defaults if there is no data in mysql - $ct ||= DateTime->now() - ; # formatter => $mysqlPattern); # do not store pattern! - it is incompat. with Storable - $mt ||= DateTime->now() - ; # formatter => $mysqlPattern); # do not store pattern! - it is incompat. with Storable + $ct ||= DateTime->now(); + $mt ||= DateTime->now(); # ct and mt are not in DateTime's internal format - -# # set formatter to output date/time in the requested format -# $ct->set_formatter($mysqlPattern); -# $mt->set_formatter($mysqlPattern); -# # finally fount it! -# # this causes to inject regexp in the object and causes problems with Storable! -# # $mysqlPattern seems to be REGEXP - do not store this in the object! -# say "Entry->SQL->all: parsing mod_time: ".$mt; - # add default my $month = $row->{month} // 0; - push @objs, - $self->e_factory->new_Entry( - old_mysql_id => $row->{id}, + my $obj = $self->e_factory->new_Entry( id => $row->{id}, + old_mysql_id => $row->{id}, entry_type => $row->{entry_type}, bibtex_key => $row->{bibtex_key}, _bibtex_type => $row->{bibtex_type}, @@ -102,7 +90,11 @@ sub all { creation_time => $ct, modified_time => $mt, need_html_regen => $row->{need_html_regen}, - ); + ); + + # Factory sets id to undef, so extra rewrite is needed + # $obj->{id} = $row->{id}; + push @objs, $obj; } return @objs; } @@ -112,7 +104,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -129,7 +121,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -137,8 +129,8 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Entry LIMIT 1;"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -146,7 +138,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -164,7 +156,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -188,7 +180,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -210,19 +202,21 @@ sub _insert { creation_time, modified_time, need_html_regen - ) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?);"; my $sth = $dbh->prepare($qry); foreach my $obj (@objects) { - + my $id = undef; + $id = $obj->id if defined $obj->id and $obj->id > 0; try { my $result = $sth->execute( - $obj->id, $obj->entry_type, $obj->bibtex_key, + $id, $obj->entry_type, $obj->bibtex_key, $obj->{_bibtex_type}, $obj->bib, $obj->html, $obj->html_bib, $obj->abstract, $obj->title, $obj->hidden, $obj->year, $obj->month, $obj->creation_time, $obj->modified_time, $obj->need_html_regen, ); + $obj->id($sth->{mysql_insertid}); } catch { $self->logger->error("Insert exception during inserting ID " @@ -232,8 +226,6 @@ sub _insert { . ". Error: $_"); }; } - - # $dbh->commit(); } before '_insert' => sub { shift->logger->entering(""); }; after '_insert' => sub { shift->logger->exiting(""); }; @@ -241,7 +233,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -290,30 +282,33 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Entry WHERE id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->id); + $result = $sth->execute($obj->id); } catch { + $result = 0; $self->logger->error("Delete exception: $_", "" . __PACKAGE__ . "->delete"); }; } - + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -328,7 +323,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/ExceptionMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/ExceptionMySQLDAO.pm index c5c5207..9236168 100644 --- a/lib/BibSpace/DAO/MySQL/ExceptionMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/ExceptionMySQLDAO.pm @@ -22,7 +22,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -35,7 +35,10 @@ sub all { while (my $row = $sth->fetchrow_hashref()) { push @objs, - Exception->new(entry_id => $row->{entry_id}, team_id => $row->{team_id}); + $self->e_factory->new_Exception( + entry_id => $row->{entry_id}, + team_id => $row->{team_id} + ); } return @objs; } @@ -45,7 +48,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -54,8 +57,7 @@ sub count { "SELECT COUNT(*) as num FROM Exceptions_Entry_to_Team LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num; + return $row->{num} || 0; } before 'count' => sub { shift->logger->entering(""); }; after 'count' => sub { shift->logger->exiting(""); }; @@ -63,7 +65,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -72,8 +74,8 @@ sub empty { = $dbh->prepare("SELECT 1 as num FROM Exceptions_Entry_to_Team LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -81,7 +83,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -101,7 +103,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -114,7 +116,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -141,7 +143,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -161,29 +163,33 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Exceptions_Entry_to_Team WHERE entry_id=? AND team_id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->entry_id, $obj->team_id); + $result = $sth->execute($obj->entry_id, $obj->team_id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -205,7 +211,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/LabelingMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/LabelingMySQLDAO.pm index cc09660..5695d6d 100644 --- a/lib/BibSpace/DAO/MySQL/LabelingMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/LabelingMySQLDAO.pm @@ -22,7 +22,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -35,7 +35,10 @@ sub all { while (my $row = $sth->fetchrow_hashref()) { push @objs, - Labeling->new(entry_id => $row->{entry_id}, tag_id => $row->{tag_id}); + $self->e_factory->new_Labeling( + entry_id => $row->{entry_id}, + tag_id => $row->{tag_id} + ); } return @objs; } @@ -45,7 +48,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -62,7 +65,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -70,8 +73,8 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Entry_to_Tag LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -79,7 +82,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -99,7 +102,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -123,7 +126,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -152,7 +155,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -172,21 +175,25 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Entry_to_Tag WHERE entry_id=? AND tag_id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->entry_id, $obj->tag_id); + $result = $sth->execute($obj->entry_id, $obj->tag_id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; @@ -194,7 +201,7 @@ after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -216,7 +223,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/MembershipMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/MembershipMySQLDAO.pm index 25f69f9..b05dec4 100644 --- a/lib/BibSpace/DAO/MySQL/MembershipMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/MembershipMySQLDAO.pm @@ -25,7 +25,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -38,7 +38,7 @@ sub all { my @memberships; while (my $row = $sth->fetchrow_hashref()) { - my $mem = Membership->new( + my $mem = $self->e_factory->new_Membership( team_id => $row->{team_id}, author_id => $row->{author_id}, start => $row->{start}, @@ -55,7 +55,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -72,7 +72,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -80,8 +80,8 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Author_to_Team LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -89,7 +89,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -109,7 +109,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -133,7 +133,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -147,6 +147,7 @@ sub _insert { $obj->stop); } catch { + $self->logger->error("Insert exception: $_"); }; } @@ -159,7 +160,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -171,7 +172,7 @@ sub update { # update field 'modified_time' only if needed my $qry = "UPDATE Author_to_Team SET start=?, - stop=? + stop=? WHERE author_id = ? AND team_id = ?"; my $sth = $dbh->prepare($qry); @@ -190,35 +191,38 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; - + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { next if !defined $obj; my $qry = "DELETE FROM Author_to_Team WHERE author_id=? AND team_id=?;"; my $sth = $dbh->prepare($qry); try { if (defined $obj->author and defined $obj->team) { - $sth->execute($obj->author->id, $obj->team->id); + $result = $sth->execute($obj->author->id, $obj->team->id); } else { - $sth->execute($obj->author_id, $obj->team_id); + $result = $sth->execute($obj->author_id, $obj->team_id); } } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -233,7 +237,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/TagMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/TagMySQLDAO.pm index 4d386e9..92b2329 100644 --- a/lib/BibSpace/DAO/MySQL/TagMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/TagMySQLDAO.pm @@ -25,7 +25,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -58,7 +58,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -75,7 +75,7 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -83,8 +83,8 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Tag LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -92,7 +92,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -111,7 +111,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -135,7 +135,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -144,9 +144,11 @@ sub _insert { INSERT INTO Tag(id, name, type, permalink) VALUES (?,?,?,?);"; my $sth = $dbh->prepare($qry); foreach my $obj (@objects) { + my $id = undef; + $id = $obj->id if defined $obj->id and $obj->id > 0; try { - my $result - = $sth->execute($obj->id, $obj->name, $obj->type, $obj->permalink); + my $result = $sth->execute($id, $obj->name, $obj->type, $obj->permalink); + $obj->id($sth->{mysql_insertid}); } catch { $self->logger->error("Insert exception: $_"); @@ -161,7 +163,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -190,28 +192,32 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Tag WHERE id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->id); + $result = $sth->execute($obj->id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -226,7 +232,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/TagTypeMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/TagTypeMySQLDAO.pm index fd11cb9..2bba1ed 100644 --- a/lib/BibSpace/DAO/MySQL/TagTypeMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/TagTypeMySQLDAO.pm @@ -25,12 +25,12 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; my $dbh = $self->handle; - my $qry = "SELECT id, name, comment + my $qry = "SELECT id, name, comment FROM TagType;"; my $sth = $dbh->prepare($qry); $sth->execute(); @@ -55,7 +55,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -80,11 +80,16 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; - return $self->count() == 0; + my $dbh = $self->handle; + my $sth = $dbh->prepare("SELECT 1 as num FROM TagType LIMIT 1;"); + $sth->execute(); + my $row = $sth->fetchrow_hashref(); + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -92,7 +97,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -116,7 +121,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -140,7 +145,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -149,8 +154,11 @@ sub _insert { INSERT INTO TagType(id, name, comment) VALUES (?,?,?);"; my $sth = $dbh->prepare($qry); foreach my $obj (@objects) { + my $id = undef; + $id = $obj->id if defined $obj->id and $obj->id > 0; try { - my $result = $sth->execute($obj->id, $obj->name, $obj->comment); + my $result = $sth->execute($id, $obj->name, $obj->comment); + $obj->id($sth->{mysql_insertid}); } catch { $self->logger->error("Insert exception: $_"); @@ -165,7 +173,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -192,28 +200,32 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM TagType WHERE id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->id); + $result = $sth->execute($obj->id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -228,7 +240,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/TeamMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/TeamMySQLDAO.pm index 7582d80..d61720d 100644 --- a/lib/BibSpace/DAO/MySQL/TeamMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/TeamMySQLDAO.pm @@ -22,7 +22,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; @@ -52,7 +52,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -75,22 +75,16 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; my $dbh = $self->handle; - my $num = 0; - try { - my $sth = $dbh->prepare("SELECT 1 as num FROM Team LIMIT 1"); - $sth->execute(); - my $row = $sth->fetchrow_hashref(); - $num = $row->{num}; - } - catch { - $self->logger->error("Count exception: $_"); - }; - return $num == 0; + my $sth = $dbh->prepare("SELECT 1 as num FROM Team LIMIT 1"); + $sth->execute(); + my $row = $sth->fetchrow_hashref(); + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; after 'empty' => sub { shift->logger->exiting(""); }; @@ -98,7 +92,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -117,7 +111,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -141,7 +135,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -150,8 +144,11 @@ sub _insert { INSERT INTO Team (id, name, parent) VALUES (?,?,?);"; my $sth = $dbh->prepare($qry); foreach my $obj (@objects) { + my $id = undef; + $id = $obj->id if defined $obj->id and $obj->id > 0; try { - my $result = $sth->execute($obj->id, $obj->name, $obj->parent); + my $result = $sth->execute($id, $obj->name, $obj->parent); + $obj->id($sth->{mysql_insertid}); } catch { $self->logger->error("Insert exception: $_"); @@ -166,7 +163,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -195,29 +192,32 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Team WHERE id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->id); + $result = $sth->execute($obj->id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } - + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -232,7 +232,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/TypeMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/TypeMySQLDAO.pm index 5f4bc0f..5d6535f 100644 --- a/lib/BibSpace/DAO/MySQL/TypeMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/TypeMySQLDAO.pm @@ -21,7 +21,7 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. -=cut +=cut sub all { my ($self) = @_; @@ -74,7 +74,7 @@ after 'all' => sub { shift->logger->exiting(""); }; =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -92,11 +92,16 @@ after 'count' => sub { shift->logger->exiting(""); }; =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; - return $self->count == 0; + my $dbh = $self->handle; + my $sth = $dbh->prepare("SELECT 1 as num FROM OurType_to_Type LIMIT 1;"); + $sth->execute(); + my $row = $sth->fetchrow_hashref(); + return 1 if not defined $row; + return; } before 'empty' => sub { shift->logger->entering(""); }; @@ -105,7 +110,7 @@ after 'empty' => sub { shift->logger->exiting(""); }; =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -125,7 +130,7 @@ after 'exists' => sub { shift->logger->exiting(""); }; =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; @@ -154,7 +159,7 @@ after '_insert' => sub { shift->logger->exiting(""); }; =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -162,13 +167,9 @@ sub save { foreach my $obj (@objects) { if ($self->exists($obj)) { $self->update($obj); - $self->logger->lowdebug( - "Updated " . ref($obj) . " ID " . $obj->id . " in DB."); } else { $self->_insert($obj); - $self->logger->lowdebug( - "Inserted " . ref($obj) . " ID " . $obj->id . " into DB."); } } } @@ -178,7 +179,7 @@ after 'save' => sub { shift->logger->exiting(""); }; =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -193,31 +194,33 @@ after 'update' => sub { shift->logger->exiting(""); }; =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; - my $qry = " + my $dbh = $self->handle; + my $result = 0; + my $qry = " DELETE FROM OurType_to_Type WHERE our_type=?;"; my $sth = $dbh->prepare($qry); foreach my $obj (@objects) { try { - my $result = $sth->execute($obj->our_type); + $result = $sth->execute($obj->our_type); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } - - # $dbh->commit(); + return 1 if $result > 0; + return; } before 'delete' => sub { shift->logger->entering(""); }; after 'delete' => sub { shift->logger->exiting(""); }; =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -233,7 +236,7 @@ after 'filter' => sub { shift->logger->exiting(""); }; =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQL/UserMySQLDAO.pm b/lib/BibSpace/DAO/MySQL/UserMySQLDAO.pm index c2b5ea9..a16f166 100644 --- a/lib/BibSpace/DAO/MySQL/UserMySQLDAO.pm +++ b/lib/BibSpace/DAO/MySQL/UserMySQLDAO.pm @@ -22,23 +22,23 @@ use Time::HiRes qw( gettimeofday tv_interval ); =item all Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub all { my ($self) = @_; my $dbh = $self->handle; - my $qry = "SELECT - id, - login, - registration_time, - last_login, - real_name, - email, - pass, - pass2, - pass3, - rank, - master_id, + my $qry = "SELECT + id, + login, + registration_time, + last_login, + real_name, + email, + pass, + pass2, + pass3, + rank, + master_id, tennant_id FROM Login ORDER BY login ASC"; @@ -60,21 +60,19 @@ sub all { while (my $row = $sth->fetchrow_hashref()) { # set formatter to parse date/time in the requested format - my $rt = $mysqlPattern->parse_datetime($row->{registration_time}); - my $ll = $mysqlPattern->parse_datetime($row->{last_login}); + my $regTime = $mysqlPattern->parse_datetime($row->{registration_time}); + my $lastLogin = $mysqlPattern->parse_datetime($row->{last_login}); # set defaults if there is no data in mysql - $rt ||= DateTime->now() - ; # formatter => $mysqlPattern); # do not store pattern! - it is incompat. with Storable - $ll ||= DateTime->now() - ; # formatter => $mysqlPattern); # do not store pattern! - it is incompat. with Storable + $regTime ||= DateTime->now(); + $lastLogin ||= DateTime->now(); my $obj = $self->e_factory->new_User( old_mysql_id => $row->{id}, id => $row->{id}, login => $row->{login}, - registration_time => $rt, - last_login => $ll, + registration_time => $regTime, + last_login => $lastLogin, real_name => $row->{real_name}, email => $row->{email}, pass => $row->{pass}, @@ -92,7 +90,7 @@ sub all { =item count Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub count { my ($self) = @_; @@ -107,7 +105,7 @@ sub count { =item empty Method documentation placeholder. This method takes no arguments and returns array or scalar. -=cut +=cut sub empty { my ($self) = @_; @@ -115,14 +113,14 @@ sub empty { my $sth = $dbh->prepare("SELECT 1 as num FROM Login LIMIT 1"); $sth->execute(); my $row = $sth->fetchrow_hashref(); - my $num = $row->{num} // 0; - return $num == 0; + return 1 if not defined $row; + return; } =item exists Method documentation placeholder. This method takes single object as argument and returns a scalar. -=cut +=cut sub exists { my ($self, $object) = @_; @@ -138,7 +136,7 @@ sub exists { =item save Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub save { my ($self, @objects) = @_; @@ -160,37 +158,39 @@ sub save { =item _insert Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub _insert { my ($self, @objects) = @_; my $dbh = $self->handle; my $qry = " INSERT INTO Login( - id, - login, - registration_time, - last_login, - real_name, - email, - pass, - pass2, - pass3, - rank, - master_id, + id, + login, + registration_time, + last_login, + real_name, + email, + pass, + pass2, + pass3, + rank, + master_id, tennant_id - ) + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?);"; my $sth = $dbh->prepare($qry); foreach my $obj (@objects) { - + my $id = undef; + $id = $obj->id if defined $obj->id and $obj->id > 0; try { my $result = $sth->execute( - $obj->id, $obj->login, $obj->registration_time, + $id, $obj->login, $obj->registration_time, $obj->last_login, $obj->real_name, $obj->email, $obj->pass, $obj->pass2, $obj->pass3, $obj->rank, $obj->master_id, $obj->tennant_id ); + $obj->id($sth->{mysql_insertid}); } catch { $self->logger->error("Insert exception: $_"); @@ -203,7 +203,7 @@ sub _insert { =item update Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub update { my ($self, @objects) = @_; @@ -213,17 +213,17 @@ sub update { next if !defined $obj->login; # update field 'modified_time' only if needed - my $qry = "UPDATE Login SET + my $qry = "UPDATE Login SET login=?, - registration_time=?, - last_login=?, - real_name=?, - email=?, - pass=?, - pass2=?, - pass3=?, - rank=?, - master_id=?, + registration_time=?, + last_login=?, + real_name=?, + email=?, + pass=?, + pass2=?, + pass3=?, + rank=?, + master_id=?, tennant_id=? WHERE id = ?"; @@ -246,27 +246,30 @@ sub update { =item delete Method documentation placeholder. This method takes single object or array of objects as argument and returns nothing. -=cut +=cut sub delete { my ($self, @objects) = @_; - my $dbh = $self->handle; + my $dbh = $self->handle; + my $result = 0; foreach my $obj (@objects) { my $qry = "DELETE FROM Login WHERE id=?;"; my $sth = $dbh->prepare($qry); try { - my $result = $sth->execute($obj->id); + $result = $sth->execute($obj->id); } catch { + $result = 0; $self->logger->error("Delete exception: $_"); }; } - + return 1 if $result > 0; + return; } =item filter Method documentation placeholder. -=cut +=cut sub filter { my ($self, $coderef) = @_; @@ -280,7 +283,7 @@ sub filter { =item find Method documentation placeholder. -=cut +=cut sub find { my ($self, $coderef) = @_; diff --git a/lib/BibSpace/DAO/MySQLDAOFactory.pm b/lib/BibSpace/DAO/MySQLDAOFactory.pm index 77b3c4e..bb6eb8e 100644 --- a/lib/BibSpace/DAO/MySQLDAOFactory.pm +++ b/lib/BibSpace/DAO/MySQLDAOFactory.pm @@ -31,8 +31,6 @@ sub getMembershipDao { e_factory => $self->e_factory ); } -before 'getMembershipDao' => sub { shift->logger->entering(""); }; -after 'getMembershipDao' => sub { shift->logger->exiting(""); }; sub getTagDao { my $self = shift; @@ -42,8 +40,6 @@ sub getTagDao { e_factory => $self->e_factory ); } -before 'getTagDao' => sub { shift->logger->entering(""); }; -after 'getTagDao' => sub { shift->logger->exiting(""); }; sub getAuthorshipDao { my $self = shift; @@ -53,8 +49,6 @@ sub getAuthorshipDao { e_factory => $self->e_factory ); } -before 'getAuthorshipDao' => sub { shift->logger->entering(""); }; -after 'getAuthorshipDao' => sub { shift->logger->exiting(""); }; sub getEntryDao { my $self = shift; @@ -64,8 +58,6 @@ sub getEntryDao { e_factory => $self->e_factory ); } -before 'getEntryDao' => sub { shift->logger->entering(""); }; -after 'getEntryDao' => sub { shift->logger->exiting(""); }; sub getTeamDao { my $self = shift; @@ -75,8 +67,6 @@ sub getTeamDao { e_factory => $self->e_factory ); } -before 'getTeamDao' => sub { shift->logger->entering(""); }; -after 'getTeamDao' => sub { shift->logger->exiting(""); }; sub getExceptionDao { my $self = shift; @@ -86,8 +76,6 @@ sub getExceptionDao { e_factory => $self->e_factory ); } -before 'getExceptionDao' => sub { shift->logger->entering(""); }; -after 'getExceptionDao' => sub { shift->logger->exiting(""); }; sub getTagTypeDao { my $self = shift; @@ -97,8 +85,6 @@ sub getTagTypeDao { e_factory => $self->e_factory ); } -before 'getTagTypeDao' => sub { shift->logger->entering(""); }; -after 'getTagTypeDao' => sub { shift->logger->exiting(""); }; sub getLabelingDao { my $self = shift; @@ -108,8 +94,6 @@ sub getLabelingDao { e_factory => $self->e_factory ); } -before 'getLabelingDao' => sub { shift->logger->entering(""); }; -after 'getLabelingDao' => sub { shift->logger->exiting(""); }; sub getAuthorDao { my $self = shift; @@ -119,8 +103,6 @@ sub getAuthorDao { e_factory => $self->e_factory ); } -before 'getAuthorDao' => sub { shift->logger->entering(""); }; -after 'getAuthorDao' => sub { shift->logger->exiting(""); }; sub getTypeDao { my $self = shift; @@ -130,8 +112,6 @@ sub getTypeDao { e_factory => $self->e_factory ); } -before 'getTypeDao' => sub { shift->logger->entering(""); }; -after 'getTypeDao' => sub { shift->logger->exiting(""); }; sub getUserDao { my $self = shift; @@ -141,8 +121,6 @@ sub getUserDao { e_factory => $self->e_factory ); } -before 'getUserDao' => sub { shift->logger->entering(""); }; -after 'getUserDao' => sub { shift->logger->exiting(""); }; __PACKAGE__->meta->make_immutable; no Moose; diff --git a/lib/BibSpace/DAO/Redis/AuthorRedisDAO.pm b/lib/BibSpace/DAO/Redis/AuthorRedisDAO.pm deleted file mode 100644 index de06cd1..0000000 --- a/lib/BibSpace/DAO/Redis/AuthorRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package AuthorRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Author; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/AuthorshipRedisDAO.pm b/lib/BibSpace/DAO/Redis/AuthorshipRedisDAO.pm deleted file mode 100644 index 713532a..0000000 --- a/lib/BibSpace/DAO/Redis/AuthorshipRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package AuthorshipRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Authorship; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/EntryRedisDAO.pm b/lib/BibSpace/DAO/Redis/EntryRedisDAO.pm deleted file mode 100644 index dc668cb..0000000 --- a/lib/BibSpace/DAO/Redis/EntryRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package EntryRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Entry; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/ExceptionRedisDAO.pm b/lib/BibSpace/DAO/Redis/ExceptionRedisDAO.pm deleted file mode 100644 index cdf079a..0000000 --- a/lib/BibSpace/DAO/Redis/ExceptionRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package ExceptionRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Exception; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/LabelingRedisDAO.pm b/lib/BibSpace/DAO/Redis/LabelingRedisDAO.pm deleted file mode 100644 index 9f3cdcf..0000000 --- a/lib/BibSpace/DAO/Redis/LabelingRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package LabelingRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Labeling; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/MembershipRedisDAO.pm b/lib/BibSpace/DAO/Redis/MembershipRedisDAO.pm deleted file mode 100644 index 5c3f7da..0000000 --- a/lib/BibSpace/DAO/Redis/MembershipRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package MembershipRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Membership; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/TagRedisDAO.pm b/lib/BibSpace/DAO/Redis/TagRedisDAO.pm deleted file mode 100644 index 476c347..0000000 --- a/lib/BibSpace/DAO/Redis/TagRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package TagRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Tag; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/TagTypeRedisDAO.pm b/lib/BibSpace/DAO/Redis/TagTypeRedisDAO.pm deleted file mode 100644 index e3bb0f6..0000000 --- a/lib/BibSpace/DAO/Redis/TagTypeRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package TagTypeRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::TagType; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/TeamRedisDAO.pm b/lib/BibSpace/DAO/Redis/TeamRedisDAO.pm deleted file mode 100644 index 5b0461d..0000000 --- a/lib/BibSpace/DAO/Redis/TeamRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package TeamRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Team; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/TypeRedisDAO.pm b/lib/BibSpace/DAO/Redis/TypeRedisDAO.pm deleted file mode 100644 index 32b9168..0000000 --- a/lib/BibSpace/DAO/Redis/TypeRedisDAO.pm +++ /dev/null @@ -1,158 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-14T17:02:11 -package BibSpace::DAO::Redis::TypeRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::ITypeDAO; -use BibSpace::Model::Type; -with 'BibSpace::DAO::Interface::ITypeDAO'; - -# Inherited fields from BibSpace::DAO::Interface::ITypeDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'BibSpace::Util::ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. -=cut - -sub all { - my ($self) = @_; - $self->logger->entering(""); - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item save - Method documentation placeholder. -=cut - -sub save { - my ($self, @objects) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item update - Method documentation placeholder. -=cut - -sub update { - my ($self, @objects) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item delete - Method documentation placeholder. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item exists - Method documentation placeholder. -=cut - -sub exists { - my ($self, @objects) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -=item count - Method documentation placeholder. -=cut - -sub count { - my ($self, $coderef) = @_; - $self->logger->entering(""); - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - $self->logger->exiting(""); -} - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/Redis/UserRedisDAO.pm b/lib/BibSpace/DAO/Redis/UserRedisDAO.pm deleted file mode 100644 index 975afed..0000000 --- a/lib/BibSpace/DAO/Redis/UserRedisDAO.pm +++ /dev/null @@ -1,185 +0,0 @@ - -package UserRedisDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::User; -with 'IDAO'; -use Try::Tiny; - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to save " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to update " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - - die "" - . (caller(0))[3] - . " not implemented. Method was instructed to delete " - . scalar(@objects) - . " objects."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - - die "" . (caller(0))[3] . " not implemented."; - - # TODO: auto-generated method stub. Implement me! - -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/RedisDAOFactory.pm b/lib/BibSpace/DAO/RedisDAOFactory.pm deleted file mode 100644 index 0a97bc4..0000000 --- a/lib/BibSpace/DAO/RedisDAOFactory.pm +++ /dev/null @@ -1,148 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T14:47:33 -package RedisDAOFactory; - -use namespace::autoclean; - -use Moose; -use BibSpace::Util::ILogger; -use BibSpace::DAO::Redis::MembershipRedisDAO; -use BibSpace::DAO::Redis::TagRedisDAO; -use BibSpace::DAO::Redis::AuthorshipRedisDAO; -use BibSpace::DAO::Redis::EntryRedisDAO; -use BibSpace::DAO::Redis::TeamRedisDAO; -use BibSpace::DAO::Redis::ExceptionRedisDAO; -use BibSpace::DAO::Redis::TagTypeRedisDAO; -use BibSpace::DAO::Redis::LabelingRedisDAO; -use BibSpace::DAO::Redis::AuthorRedisDAO; -use BibSpace::DAO::Redis::UserRedisDAO; -use BibSpace::DAO::DAOFactory; -extends 'DAOFactory'; - -has 'handle' => (is => 'ro', required => 1); -has 'logger' => (is => 'ro', does => 'ILogger', required => 1); -has 'e_factory' => (is => 'ro', isa => 'EntityFactory', required => 1); - -sub getMembershipDao { - my $self = shift; - return MembershipRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getMembershipDao' => sub { shift->logger->entering(""); }; -after 'getMembershipDao' => sub { shift->logger->exiting(""); }; - -sub getTagDao { - my $self = shift; - return TagRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTagDao' => sub { shift->logger->entering(""); }; -after 'getTagDao' => sub { shift->logger->exiting(""); }; - -sub getAuthorshipDao { - my $self = shift; - return AuthorshipRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getAuthorshipDao' => sub { shift->logger->entering(""); }; -after 'getAuthorshipDao' => sub { shift->logger->exiting(""); }; - -sub getEntryDao { - my $self = shift; - return EntryRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getEntryDao' => sub { shift->logger->entering(""); }; -after 'getEntryDao' => sub { shift->logger->exiting(""); }; - -sub getTeamDao { - my $self = shift; - return TeamRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTeamDao' => sub { shift->logger->entering(""); }; -after 'getTeamDao' => sub { shift->logger->exiting(""); }; - -sub getExceptionDao { - my $self = shift; - return ExceptionRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getExceptionDao' => sub { shift->logger->entering(""); }; -after 'getExceptionDao' => sub { shift->logger->exiting(""); }; - -sub getTagTypeDao { - my $self = shift; - return TagTypeRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTagTypeDao' => sub { shift->logger->entering(""); }; -after 'getTagTypeDao' => sub { shift->logger->exiting(""); }; - -sub getLabelingDao { - my $self = shift; - return LabelingRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getLabelingDao' => sub { shift->logger->entering(""); }; -after 'getLabelingDao' => sub { shift->logger->exiting(""); }; - -sub getAuthorDao { - my $self = shift; - return AuthorRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getAuthorDao' => sub { shift->logger->entering(""); }; -after 'getAuthorDao' => sub { shift->logger->exiting(""); }; - -sub getTypeDao { - my $self = shift; - return TypeRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTypeDao' => sub { shift->logger->entering(""); }; -after 'getTypeDao' => sub { shift->logger->exiting(""); }; - -sub getUserDao { - my $self = shift; - return UserRedisDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getUserDao' => sub { shift->logger->entering(""); }; -after 'getUserDao' => sub { shift->logger->exiting(""); }; - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/AuthorSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/AuthorSmartArrayDAO.pm deleted file mode 100644 index 7e2ba08..0000000 --- a/lib/BibSpace/DAO/SmartArray/AuthorSmartArrayDAO.pm +++ /dev/null @@ -1,140 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package AuthorSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Author; -with 'IDAO'; -use Try::Tiny; -use List::MoreUtils qw(any uniq); -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - return $self->handle->all("Author"); -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Author"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Author"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Author", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Author", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/AuthorshipSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/AuthorshipSmartArrayDAO.pm deleted file mode 100644 index 681130a..0000000 --- a/lib/BibSpace/DAO/SmartArray/AuthorshipSmartArrayDAO.pm +++ /dev/null @@ -1,141 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package AuthorshipSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Authorship; -with 'IDAO'; -use Try::Tiny; -use List::MoreUtils qw(any uniq); -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - return $self->handle->all("Authorship"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Authorship"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Authorship"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Authorship", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Authorship", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/EntrySmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/EntrySmartArrayDAO.pm deleted file mode 100644 index 2d1cca7..0000000 --- a/lib/BibSpace/DAO/SmartArray/EntrySmartArrayDAO.pm +++ /dev/null @@ -1,142 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package EntrySmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Entry; -with 'IDAO'; -use Try::Tiny; -use List::MoreUtils qw(any uniq); -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - return $self->handle->all("Entry"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Entry"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Entry"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Entry", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Entry", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/ExceptionSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/ExceptionSmartArrayDAO.pm deleted file mode 100644 index a06dee2..0000000 --- a/lib/BibSpace/DAO/SmartArray/ExceptionSmartArrayDAO.pm +++ /dev/null @@ -1,142 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package ExceptionSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Exception; -with 'IDAO'; -use Try::Tiny; -use List::MoreUtils qw(any uniq); -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - return $self->handle->all("Exception"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Exception"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Exception"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Exception", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Exception", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/LabelingSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/LabelingSmartArrayDAO.pm deleted file mode 100644 index 441a3c2..0000000 --- a/lib/BibSpace/DAO/SmartArray/LabelingSmartArrayDAO.pm +++ /dev/null @@ -1,146 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package LabelingSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Labeling; -with 'IDAO'; -use Try::Tiny; -use List::MoreUtils qw(any uniq); -use List::Util qw(first); -use feature qw( state say ); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - return $self->handle->all("Labeling"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Labeling"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Labeling"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - - my %existing = map { $_->id => 1 } $self->all; - @objects = grep { not $existing{$_->id} } @objects; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Labeling", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Labeling", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/MembershipSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/MembershipSmartArrayDAO.pm deleted file mode 100644 index 229f372..0000000 --- a/lib/BibSpace/DAO/SmartArray/MembershipSmartArrayDAO.pm +++ /dev/null @@ -1,139 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package MembershipSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Membership; -with 'IDAO'; -use Try::Tiny; -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - return $self->handle->all("Membership"); -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Membership"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Membership"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Membership", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Membership", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/TagSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/TagSmartArrayDAO.pm deleted file mode 100644 index 6d433bf..0000000 --- a/lib/BibSpace/DAO/SmartArray/TagSmartArrayDAO.pm +++ /dev/null @@ -1,141 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package TagSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Tag; -with 'IDAO'; -use Try::Tiny; -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - return $self->handle->all("Tag"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Tag"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Tag"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Tag", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Tag", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/TagTypeSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/TagTypeSmartArrayDAO.pm deleted file mode 100644 index fe13d48..0000000 --- a/lib/BibSpace/DAO/SmartArray/TagTypeSmartArrayDAO.pm +++ /dev/null @@ -1,141 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package TagTypeSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::TagType; -with 'IDAO'; -use Try::Tiny; -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - return $self->handle->all("TagType"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("TagType"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("TagType"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("TagType", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("TagType", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/TeamSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/TeamSmartArrayDAO.pm deleted file mode 100644 index b7c967b..0000000 --- a/lib/BibSpace/DAO/SmartArray/TeamSmartArrayDAO.pm +++ /dev/null @@ -1,139 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package TeamSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Team; -with 'IDAO'; -use Try::Tiny; -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - return $self->handle->all("Team"); -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Team"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Team"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Team", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Team", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/TypeSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/TypeSmartArrayDAO.pm deleted file mode 100644 index 782b4dc..0000000 --- a/lib/BibSpace/DAO/SmartArray/TypeSmartArrayDAO.pm +++ /dev/null @@ -1,139 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package TypeSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::Type; -with 'IDAO'; -use Try::Tiny; -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - return $self->handle->all("Type"); -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("Type"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("Type"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("Type", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("Type", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArray/UserSmartArrayDAO.pm b/lib/BibSpace/DAO/SmartArray/UserSmartArrayDAO.pm deleted file mode 100644 index 3242203..0000000 --- a/lib/BibSpace/DAO/SmartArray/UserSmartArrayDAO.pm +++ /dev/null @@ -1,142 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package UserSmartArrayDAO; - -use namespace::autoclean; - -use Moose; -use BibSpace::DAO::Interface::IDAO; -use BibSpace::Model::User; -with 'IDAO'; -use Try::Tiny; -use List::MoreUtils qw(any uniq); -use List::Util qw(first); - -# Inherited fields from BibSpace::DAO::Interface::IDAO Mixin: -# has 'logger' => ( is => 'ro', does => 'ILogger', required => 1); -# has 'handle' => ( is => 'ro', required => 1); - -=item all - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub all { - my ($self) = @_; - - return $self->handle->all("User"); - -} -before 'all' => sub { shift->logger->entering(""); }; -after 'all' => sub { shift->logger->exiting(""); }; - -=item count - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub count { - my ($self) = @_; - return $self->handle->count("User"); -} -before 'count' => sub { shift->logger->entering(""); }; -after 'count' => sub { shift->logger->exiting(""); }; - -=item empty - Method documentation placeholder. - This method takes no arguments and returns array or scalar. -=cut - -sub empty { - my ($self) = @_; - return $self->handle->empty("User"); -} -before 'empty' => sub { shift->logger->entering(""); }; -after 'empty' => sub { shift->logger->exiting(""); }; - -=item exists - Method documentation placeholder. - This method takes single object as argument and returns a scalar. -=cut - -sub exists { - my ($self, $object) = @_; - $self->handle->exists($object); -} -before 'exists' => sub { shift->logger->entering(""); }; -after 'exists' => sub { shift->logger->exiting(""); }; - -=item save - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub save { - my ($self, @objects) = @_; - $self->handle->save(@objects); -} -before 'save' => sub { shift->logger->entering(""); }; -after 'save' => sub { shift->logger->exiting(""); }; - -=item update - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub update { - my ($self, @objects) = @_; - - # smart array does not require updating! Objects are direct references! -} -before 'update' => sub { shift->logger->entering(""); }; -after 'update' => sub { shift->logger->exiting(""); }; - -=item delete - Method documentation placeholder. - This method takes single object or array of objects as argument and returns nothing. -=cut - -sub delete { - my ($self, @objects) = @_; - $self->handle->delete(@objects); -} -before 'delete' => sub { shift->logger->entering(""); }; -after 'delete' => sub { shift->logger->exiting(""); }; - -=item filter - Method documentation placeholder. -=cut - -sub filter { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->filter("User", $coderef); -} -before 'filter' => sub { shift->logger->entering(""); }; -after 'filter' => sub { shift->logger->exiting(""); }; - -=item find - Method documentation placeholder. -=cut - -sub find { - my ($self, $coderef) = @_; - die "" - . (caller(0))[3] - . " incorrect type of argument. Got: '" - . ref($coderef) - . "', expected: " - . (ref sub { }) . "." - unless (ref $coderef eq ref sub { }); - return $self->handle->find("User", $coderef); -} -before 'find' => sub { shift->logger->entering(""); }; -after 'find' => sub { shift->logger->exiting(""); }; -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/DAO/SmartArrayDAOFactory.pm b/lib/BibSpace/DAO/SmartArrayDAOFactory.pm deleted file mode 100644 index 46c4683..0000000 --- a/lib/BibSpace/DAO/SmartArrayDAOFactory.pm +++ /dev/null @@ -1,149 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T16:44:28 -package SmartArrayDAOFactory; - -use namespace::autoclean; - -use Moose; -use BibSpace::Util::ILogger; -use BibSpace::DAO::SmartArray::TagTypeSmartArrayDAO; -use BibSpace::DAO::SmartArray::TeamSmartArrayDAO; -use BibSpace::DAO::SmartArray::AuthorSmartArrayDAO; -use BibSpace::DAO::SmartArray::AuthorshipSmartArrayDAO; -use BibSpace::DAO::SmartArray::MembershipSmartArrayDAO; -use BibSpace::DAO::SmartArray::EntrySmartArrayDAO; -use BibSpace::DAO::SmartArray::LabelingSmartArrayDAO; -use BibSpace::DAO::SmartArray::TagSmartArrayDAO; -use BibSpace::DAO::SmartArray::ExceptionSmartArrayDAO; -use BibSpace::DAO::SmartArray::TypeSmartArrayDAO; -use BibSpace::DAO::SmartArray::UserSmartArrayDAO; -use BibSpace::DAO::DAOFactory; -extends 'DAOFactory'; - -has 'handle' => (is => 'ro', required => 1); -has 'logger' => (is => 'ro', does => 'ILogger', required => 1); -has 'e_factory' => (is => 'ro', isa => 'EntityFactory', required => 1); - -sub getTagTypeDao { - my $self = shift; - return TagTypeSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTagTypeDao' => sub { shift->logger->entering(""); }; -after 'getTagTypeDao' => sub { shift->logger->exiting(""); }; - -sub getTeamDao { - my $self = shift; - return TeamSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTeamDao' => sub { shift->logger->entering(""); }; -after 'getTeamDao' => sub { shift->logger->exiting(""); }; - -sub getAuthorDao { - my $self = shift; - return AuthorSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getAuthorDao' => sub { shift->logger->entering(""); }; -after 'getAuthorDao' => sub { shift->logger->exiting(""); }; - -sub getAuthorshipDao { - my $self = shift; - return AuthorshipSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getAuthorshipDao' => sub { shift->logger->entering(""); }; -after 'getAuthorshipDao' => sub { shift->logger->exiting(""); }; - -sub getMembershipDao { - my $self = shift; - return MembershipSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getMembershipDao' => sub { shift->logger->entering(""); }; -after 'getMembershipDao' => sub { shift->logger->exiting(""); }; - -sub getEntryDao { - my $self = shift; - return EntrySmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getEntryDao' => sub { shift->logger->entering(""); }; -after 'getEntryDao' => sub { shift->logger->exiting(""); }; - -sub getLabelingDao { - my $self = shift; - return LabelingSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getLabelingDao' => sub { shift->logger->entering(""); }; -after 'getLabelingDao' => sub { shift->logger->exiting(""); }; - -sub getTagDao { - my $self = shift; - return TagSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTagDao' => sub { shift->logger->entering(""); }; -after 'getTagDao' => sub { shift->logger->exiting(""); }; - -sub getExceptionDao { - my $self = shift; - return ExceptionSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getExceptionDao' => sub { shift->logger->entering(""); }; -after 'getExceptionDao' => sub { shift->logger->exiting(""); }; - -sub getTypeDao { - my $self = shift; - return TypeSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getTypeDao' => sub { shift->logger->entering(""); }; -after 'getTypeDao' => sub { shift->logger->exiting(""); }; - -sub getUserDao { - my $self = shift; - return UserSmartArrayDAO->new( - logger => $self->logger, - handle => $self->handle, - e_factory => $self->e_factory - ); -} -before 'getUserDao' => sub { shift->logger->entering(""); }; -after 'getUserDao' => sub { shift->logger->exiting(""); }; - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/Functions/BackupFunctions.pm b/lib/BibSpace/Functions/BackupFunctions.pm index 9b118d4..b700a85 100644 --- a/lib/BibSpace/Functions/BackupFunctions.pm +++ b/lib/BibSpace/Functions/BackupFunctions.pm @@ -4,11 +4,9 @@ use BibSpace::Functions::MySqlBackupFunctions; use BibSpace::Model::Backup; use BibSpace::Functions::Core; use BibSpace::Functions::FDB; -use BibSpace::Backend::SmartBackendHelper; use JSON -convert_blessed_universally; use BibSpace::Model::SerializableBase::BibSpaceDTO; -use Storable; use Data::Dumper; use utf8; @@ -30,11 +28,9 @@ our @ISA = qw( Exporter ); our @EXPORT = qw( find_backup read_backups - do_storable_backup do_json_backup do_mysql_backup restore_json_backup - restore_storable_backup delete_old_backups ); ## Trivial DAO FIND @@ -87,25 +83,6 @@ sub do_json_backup { return $backup; } -sub do_storable_backup { - my $app = shift; - my $name = shift // 'normal'; - - my $backup_dir = Path::Tiny->new($app->get_backups_dir)->relative; - $backup_dir =~ s!/*$!/!; # hy do I need to add this??? - - my $backup = Backup->create($name, "storable"); - $backup->dir("" . $backup_dir); - - my $layer = $app->repo->lr->get_read_layer; - my $path = "" . $backup->get_path; - - $Storable::forgive_me = "do store regexp please"; - Storable::store $layer, $path; - - return $backup; -} - sub do_mysql_backup { my $app = shift; my $name = shift // 'normal'; @@ -138,12 +115,8 @@ sub restore_json_backup { my $dto = BibSpaceDTO->new(); my $decodedDTO; try { - $decodedDTO = $dto->toLayeredRepo( - $jsonString, - $app->repo->lr->uidProvider, - $app->repo->lr->preferences - ); - $success = 1; + $decodedDTO = $dto->toLayeredRepo($jsonString, $app->repo); + $success = 1; } catch { $app->logger->error("Exception during JSON restore: $_"); @@ -155,8 +128,6 @@ sub restore_json_backup { my @layers = $app->repo->lr->get_all_layers; foreach (@layers) { $_->reset_data } - # $app->repo->lr->reset_uid_providers; - for my $type (@{$app->repo->entities}, @{$app->repo->relations}) { my $arrayRef = $decodedDTO->data->{$type}; @@ -190,45 +161,10 @@ sub restore_json_backup { }; } } - BibSpace::Backend::SmartBackendHelper::linkData($app); } return $success; } -sub restore_storable_backup { - my $backup = shift; - my $app = shift; - - my $layer = retrieve($backup->get_path); - - say "restore_storable_backup has retrieved:" . $layer->get_summary_table; - - ## this writes to all layers!! - - my @layers = $app->repo->lr->get_all_layers; - foreach (@layers) { $_->reset_data } - $app->repo->lr->reset_uid_providers; - -# say "Smart layer after reset:" . $app->repo->lr->get_layer('smart')->get_summary_table; - - my $layer_to_replace = $app->repo->lr->get_read_layer; - $app->repo->lr->replace_layer($layer_to_replace->name, $layer); - - foreach my $layer (@layers) { - next if $layer->name eq $layer_to_replace->name; - - $app->repo->lr->copy_data( - {from => $layer_to_replace->name, to => $layer->name}); - } - -# say "Smart layer after replace:" . $app->repo->lr->get_layer('smart')->get_summary_table; - - say "restore_storable_backup DONE. Smart layer after copy_data:" - . $app->repo->lr->get_layer('smart')->get_summary_table; - say "restore_storable_backup DONE. All layer after copy_data:" - . $app->repo->lr->get_summary_table; -} - sub delete_old_backups { my $app = shift; my $age_treshold = shift diff --git a/lib/BibSpace/Functions/Core.pm b/lib/BibSpace/Functions/Core.pm index edce8b2..60cf4fc 100644 --- a/lib/BibSpace/Functions/Core.pm +++ b/lib/BibSpace/Functions/Core.pm @@ -119,18 +119,6 @@ sub fix_bibtex_national_characters { # makes sth \ss sth --> sth {\ss} sth $str =~ s/(?install_driver("mysql"); - -# say "!!! DROPPING DATABASE '$db_database'."; -# try { -# my $rc = $drh->func( 'dropdb', $db_database, $db_host, $db_user, $db_pass, 'admin' ); -# } -# catch { -# warn $_; -# }; - -# say "!!! CREATING DATABASE '$db_database'."; -# try { -# my $rc = $drh->func( 'createdb', $db_database, $db_host, $db_user, $db_pass, 'admin' ); -# } -# catch { -# warn $_; -# }; - - # say "Restarting connection to '$db_database'."; - # try { - # $dbh = db_connect( $db_host, $db_user, $db_database, $db_pass ); - # create_main_db($dbh); - - # } - # catch { - # warn $_; - # }; - - return $dbh; -} - sub create_main_db { my $dbh = shift; @@ -262,12 +221,7 @@ sub create_main_db { # prepare_token_table_mysql($dbh); prepare_cron_table($dbh); prepare_user_table_mysql($dbh); - -# this causes desynchronisation between layers!! -# mysql has some initial data, whereas smart doesnt (so id providers are unaware of the data as well) - populate_tables($dbh); - - # $dbh->commit(); + apply_migrations($dbh); } # sub prepare_token_table_mysql { @@ -359,42 +313,17 @@ sub prepare_user_table_mysql { }; } -sub populate_tables { +sub apply_migrations { my $dbh = shift; try { -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('incollection','inproceedings',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('incollection','bibtex-incollection',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('inproceedings','bibtex-inproceedings',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('inbook','book',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('mastersthesis','theses',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('phdthesis','theses',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('phdthesis','volumes',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('proceedings','volumes',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('article','article',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('book','book',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('inbook','inbook',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('incollection','incollection',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('inproceedings','inproceedings',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('manual','manual','Manuals',1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('mastersthesis','mastersthesis',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('misc','misc',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('phdthesis','phdthesis',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('proceedings','proceedings',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('techreport','techreport',NULL,1)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('unpublished','unpublished',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('book','volumes',NULL,0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('mastersthesis','supervised_theses','Supervised Theses',0)"); -# $dbh->do("INSERT IGNORE INTO OurType_to_Type VALUES('phdthesis','supervised_theses','Supervised Theses',0)"); - -# $dbh->do("INSERT IGNORE INTO `TagType` VALUES ('Tag','keyword',1)"); -# $dbh->do("INSERT IGNORE INTO `TagType` VALUES ('Category','12 categories defined as in research agenda',2)"); -# $dbh->do("INSERT IGNORE INTO `TagType` VALUES ('Other','Reserved for other groupings of papers',3)"); + say "Attempt to migrate table Author"; + $dbh->do( + "ALTER TABLE `Author` DROP PRIMARY KEY, MODIFY id INTEGER(8) PRIMARY KEY AUTO_INCREMENT" + ); } catch { - say "Data already exist. Doing nothing."; + say "Migration not necessary or failed: $_"; }; - - # $dbh->commit(); } 1; diff --git a/lib/BibSpace/Functions/FPublications.pm b/lib/BibSpace/Functions/FPublications.pm index a5e0dda..c15e9c5 100644 --- a/lib/BibSpace/Functions/FPublications.pm +++ b/lib/BibSpace/Functions/FPublications.pm @@ -53,7 +53,7 @@ sub Freassign_authors_to_entries_given_by_array { } if ($create_new == 1 or defined $author) { - my $authorship = Authorship->new( + my $authorship = $app->entityFactory->new_Authorship( author => $author->get_master, entry => $entry, author_id => $author->get_master->id, @@ -246,20 +246,20 @@ sub Fget_publications_core { my $query_tag = shift; my $query_team = shift; my $query_permalink = shift; - #<<< no perltidy here - my $query_visible = shift // 0; # value can be set only from code (not from browser) - my $query_hidden = shift; # value can be set only from code (not from browser) - my $debug = shift // 0; # value can be set only from code (not from browser) - - # catch bad urls like: ...&entry_type=&tag=&author= - $query_author = undef if defined $query_author and length( "".$query_author ) < 1; - $query_year = undef if defined $query_year and length( "".$query_year ) < 1; - $query_bibtex_type = undef if defined $query_bibtex_type and length( "".$query_bibtex_type ) < 1; - $query_entry_type = undef if defined $query_entry_type and length( "".$query_entry_type ) < 1; - $query_tag = undef if defined $query_tag and length( "".$query_tag ) < 1; - $query_team = undef if defined $query_team and length( "".$query_team ) < 1; - $query_permalink = undef if defined $query_permalink and length( "".$query_permalink ) < 1; - #>>> + #<<< no perltidy here + my $query_visible = shift // 0; # value can be set only from code (not from browser) + my $query_hidden = shift; # value can be set only from code (not from browser) + my $debug = shift // 0; # value can be set only from code (not from browser) + + # catch bad urls like: ...&entry_type=&tag=&author= + $query_author = undef if defined $query_author and length( "".$query_author ) < 1; + $query_year = undef if defined $query_year and length( "".$query_year ) < 1; + $query_bibtex_type = undef if defined $query_bibtex_type and length( "".$query_bibtex_type ) < 1; + $query_entry_type = undef if defined $query_entry_type and length( "".$query_entry_type ) < 1; + $query_tag = undef if defined $query_tag and length( "".$query_tag ) < 1; + $query_team = undef if defined $query_team and length( "".$query_team ) < 1; + $query_permalink = undef if defined $query_permalink and length( "".$query_permalink ) < 1; + #>>> if ($debug == 1) { $self->app->logger->debug(Dumper $self->req->params); @@ -305,14 +305,14 @@ sub Fget_publications_core { if (defined $query_author) { if (Scalar::Util::looks_like_number($query_author)) { $author_obj - = $self->app->repo->authors_find(sub { $_->master_id == $query_author } + = $self->app->repo->authors_find(sub { $_->master->id == $query_author } ); $author_obj ||= $self->app->repo->authors_find(sub { $_->id == $query_author }); } else { - $author_obj - = $self->app->repo->authors_find(sub { $_->master eq $query_author }); + $author_obj = $self->app->repo->authors_find( + sub { $_->master->name eq $query_author }); $author_obj ||= $self->app->repo->authors_find(sub { $_->uid eq $query_author }); } @@ -395,7 +395,7 @@ sub Fget_publications_core { # Entries of visible authors # by default, we return entries of all authors if (defined $query_visible and $query_visible == 1) { - @entries = grep { $_->is_visible } @entries; + @entries = grep { $_->belongs_to_visible_author } @entries; } ######## complex filters diff --git a/lib/BibSpace/Functions/MySqlBackupFunctions.pm b/lib/BibSpace/Functions/MySqlBackupFunctions.pm index bc2c375..32efd1c 100644 --- a/lib/BibSpace/Functions/MySqlBackupFunctions.pm +++ b/lib/BibSpace/Functions/MySqlBackupFunctions.pm @@ -60,67 +60,4 @@ sub dump_mysql_to_file { return $fname; } -# This is the old function (version <0.5) used to restroee backups. -# It does not work with all MySQL server configurations, so we dont use it. -# sub do_restore_backup_from_file { -# my $app = shift; -# my $dbh = shift; -# my $file_path = shift; -# my $config = shift; - -# # I assume that $file_path is the SQL dump that I want to restore - -# my $file_exists = 0; -# if ( -e $file_path ) { -# $file_exists = 1; -# } -# else { -# $app->logger->warn("Cannot restore database from file $file_path. I stop now."); -# return; -# } - -# try{ -# $dbh->{mysql_auto_reconnect} = 0; -# $dbh->disconnect(); -# } -# catch{ -# $app->logger->error("Cannot disconnect: $_"); -# }; - -# my $db_host = $config->{db_host}; -# my $db_user = $config->{db_user}; -# my $db_database = $config->{db_database}; -# my $db_pass = $config->{db_pass}; - -# my $cmd = "mysql -u $db_user -p$db_pass $db_database < $file_path"; -# if ( $db_pass =~ /^\s*$/ ) { # password empty -# $cmd = "mysql -u $db_user $db_database < $file_path"; -# } -# my $command_output = ""; -# try { -# $command_output = `$cmd`; -# } -# catch { -# $app->logger->error("Restoring DB failed from file $file_path. Reason: $_. Status? $?. Command_output: $command_output."); -# db_connect($db_host, $db_user, $db_database, $db_pass); -# $app->db; # this will reconnect -# $app->db->{mysql_auto_reconnect} = 1; -# }; - -# $app->db(); # this will reconnect -# $app->db->{mysql_auto_reconnect} = 1; - -# if ( $? == 0 ) { -# $app->repo->hardReset; -# $app->setup_repositories; - -# $app->logger->info("Restoring backup succeeded from file $file_path"); -# return 1; -# } -# else { -# $app->logger->error("Restoring backup FAILED from file $file_path"); -# return; -# } -# } - 1; diff --git a/lib/BibSpace/Model/Author.pm b/lib/BibSpace/Model/Author.pm index 2a47297..eadbdbb 100644 --- a/lib/BibSpace/Model/Author.pm +++ b/lib/BibSpace/Model/Author.pm @@ -8,6 +8,7 @@ use List::MoreUtils qw(any uniq); use BibSpace::Model::Membership; use Moose; use MooseX::Storage; +use MooseX::Privacy; with Storage; require BibSpace::Model::IEntity; require BibSpace::Model::IAuthored; @@ -16,41 +17,28 @@ with 'IEntity', 'IAuthored', 'IMembered'; use BibSpace::Model::SerializableBase::AuthorSerializableBase; extends 'AuthorSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - return AuthorSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; -} - -has 'master' => ( - is => 'rw', - isa => 'Maybe[Str]', - default => sub { shift->{uid} }, - traits => ['DoNotSerialize'], - documentation => q{Author master name. Redundant field.} -); - +# A placeholder for master object. This will be lazily populated on first read has 'masterObj' => ( is => 'rw', isa => 'Maybe[Author]', default => sub {undef}, - traits => ['DoNotSerialize'], + traits => [qw/DoNotSerialize/, qw/Private/], documentation => q{Author's master author object.} ); # called after the default constructor sub BUILD { my $self = shift; - $self->id; # trigger lazy execution of idProvider - if ((!defined $self->master) or ($self->master eq '')) { - $self->master($self->uid); - } - if ((!defined $self->master_id) and (defined $self->{id})) { - $self->master_id($self->id); - } - if ((defined $self->masterObj) and ($self->masterObj == $self)) { - $self->masterObj(undef); + $self->name($self->uid); +} + +# Entitiy receives ID first on save to DB +# This can be fixed with generating UUID on object creation and referencing master using uuid as FK +sub post_insert_hook { + my $self = shift; + if (defined $self->id and $self->id > 0) { + $self->get_master_id; # sets master_id if unset + $self->repo->authors_update($self); } } @@ -66,37 +54,36 @@ sub equals { return $result; } -sub set_master { - my $self = shift; - my $master_author = shift; - - $self->masterObj($master_author); - - $self->master($master_author->uid); - $self->master_id($master_author->id); +sub master_name { + my $self = shift; + return $self->get_master->uid; } -sub get_master { +sub master { my $self = shift; + return $self if not $self->master_id; + return $self if not $self->masterObj; + return $self->masterObj; +} - return $self if $self->is_master; - return $self->masterObj if $self->masterObj; +sub set_master { + my $self = shift; + my $master = shift; + if (not $master->id) { + warn "Cannot set_master if master has no ID"; + return; + } + $self->masterObj($master); + $self->master_id($master->id); +} - warn "SERIOUS WARNING! Cannot derive master for " - . $self->uid - . ". Author is not master, but masterObj is undef. id '" - . $self->id - . "' master_id '" - . $self->master_id . "'."; - return; +sub get_master { + shift->master; } sub is_master { my $self = shift; - - if ($self->equals($self->masterObj) or $self->master_id == $self->id) { - return 1; - } + return 1 if ($self->id == $self->master_id); return; } @@ -108,34 +95,22 @@ sub is_minion { sub is_minion_of { my $self = shift; my $master = shift; - - if ($self->masterObj and $self->masterObj->equals($master)) { - return 1; - } + return 1 if $self->master_id == $master->id; return; } -sub update_master_name { +sub update_name { my $self = shift; my $new_master = shift; $self->uid($new_master); - - if ($self->is_minion) { - # - } - else { - $self->master($new_master); - } + $self->name($new_master); return 1; } sub remove_master { my $self = shift; - - $self->masterObj(undef); - $self->master($self->uid); - $self->master_id($self->id); + $self->set_master($self); } sub add_minion { @@ -152,6 +127,8 @@ sub can_merge_authors { my $source_author = shift; if ( (defined $source_author) + and (defined $source_author->id) + and (defined $self->id) and ($source_author->id != $self->id) and (!$self->equals($source_author))) { @@ -188,15 +165,49 @@ sub can_be_deleted { return; } +sub has_team { + my $self = shift; + my $team = shift; + return grep { $_->id eq $team->id } $self->get_teams; +} + +sub get_teams { + my $self = shift; + my @team_ids + = map { $_->team_id } + $self->repo->memberships_filter( + sub { $_->author_id == $self->id or $_->author_id == $self->get_master_id } + ); + return $self->repo->teams_filter( + sub { + my $t = $_; + return grep { $_ eq $t->id } @team_ids; + } + ); +} + +sub get_entries { + my $self = shift; + my @entry_ids = map { $_->entry_id } + $self->repo->authorships_filter(sub { $_->author_id == $self->id }); + return $self->repo->entries_filter( + sub { + my $e = $_; + return grep { $_ eq $e->id } @entry_ids; + } + ); +} + sub has_entry { my $self = shift; my $entry = shift; - my $authorship = $self->authorships_find( - sub { - $_->entry->equals($entry) and $_->author->equals($self); - } + my $authorship_to_find = $self->repo->entityFactory->new_Authorship( + author_id => $self->id, + entry_id => $entry->id ); + my $authorship + = $self->repo->authorships_find(sub { $_->equals_id($authorship_to_find) }); return defined $authorship; } @@ -208,13 +219,11 @@ sub joined_team { return -1 if !defined $team; - my $query_mem = Membership->new( - author => $self->get_master, - team => $team, + my $query_mem = $self->repo->entityFactory->new_Membership( + team_id => $team->id, author_id => $self->get_master->id, - team_id => $team->id ); - my $mem = $self->memberships_find(sub { $_->equals($query_mem) }); + my $mem = $self->repo->memberships_find(sub { $_->equals($query_mem) }); return -1 if !defined $mem; return $mem->start; @@ -226,13 +235,11 @@ sub left_team { return -1 if !defined $team; - my $query_mem = Membership->new( - author => $self->get_master, - team => $team, + my $query_mem = $self->repo->entityFactory->new_Membership( author_id => $self->get_master->id, team_id => $team->id ); - my $mem = $self->memberships_find(sub { $_->equals($query_mem) }); + my $mem = $self->repo->memberships_find(sub { $_->equals($query_mem) }); return -1 if !defined $mem; return $mem->stop; @@ -246,24 +253,27 @@ sub update_membership { return if !$team; - my $query_mem_master = Membership->new( - author => $self->get_master, - team => $team, + my $query_mem_master = $self->repo->entityFactory->new_Membership( author_id => $self->get_master->id, team_id => $team->id ); - my $query_mem_minor = Membership->new( - author => $self, - team => $team, + my $query_mem_minor = $self->repo->entityFactory->new_Membership( author_id => $self->id, team_id => $team->id ); my $mem_master - = $self->memberships_find(sub { $_->equals($query_mem_master) }); - my $mem_minor = $self->memberships_find(sub { $_->equals($query_mem_minor) }); + = $self->repo->memberships_find(sub { $_->equals($query_mem_master) }); + my $mem_minor + = $self->repo->memberships_find(sub { $_->equals($query_mem_minor) }); - if ($mem_minor != $mem_master) { - warn "MEMBERSHIP for master differs to membership of minor!"; + if ( defined $mem_minor + and defined $mem_master + and not $mem_minor->equals($mem_master)) + { + warn "MEMBERSHIP for master differs to membership of minor! master has " + . $mem_master->id + . ", minor has " + . $mem_minor->id; } my $mem = $mem_master // $mem_minor; @@ -282,19 +292,25 @@ sub update_membership { $mem->start($start) if defined $start; $mem->stop($stop) if defined $stop; + $self->repo->memberships_update($mem); return 1; } #################################################################################### TAGS -sub get_tags { +sub get_tags_of_type { + my $self = shift; + my $tag_type = shift // 1; + return grep { $_->type == $tag_type } $self->get_tags; +} - my $self = shift; - my $type = shift // 1; +sub get_tags { + my $self = shift; + my $potentialType = shift; + die "use entry->get_tags_of_type instead" if defined $potentialType; my @myTags; - - map { push @myTags, $_->get_tags($type) } $self->get_entries; + map { push @myTags, $_->get_tags } $self->get_entries; @myTags = uniq @myTags; return @myTags; diff --git a/lib/BibSpace/Model/Authorship.pm b/lib/BibSpace/Model/Authorship.pm index d2f2661..c138d0b 100644 --- a/lib/BibSpace/Model/Authorship.pm +++ b/lib/BibSpace/Model/Authorship.pm @@ -1,77 +1,44 @@ package Authorship; -use Data::Dumper; use utf8; use v5.16; use BibSpace::Model::Author; use BibSpace::Model::Entry; use BibSpace::Model::IRelation; use Try::Tiny; +use Data::Dumper; +$Data::Dumper::Maxdepth = 2; use Moose; with 'IRelation'; - use BibSpace::Model::SerializableBase::AuthorshipSerializableBase; extends 'AuthorshipSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { +# # the fileds below are used for linking process +# has 'entry_id' => (is => 'ro', isa => 'Int'); +# has 'author_id' => (is => 'ro', isa => 'Int'); + +sub author { my $self = shift; - my $copy = $self->meta->clone_object($self); - return AuthorshipSerializableBase->meta->rebless_instance_back($copy) - ->TO_JSON; + return $self->repo->authors_find(sub { $_->id == $self->author_id }); } -# the fileds below are used for linking process -has 'entry_id' => (is => 'ro', isa => 'Int'); -has 'author_id' => (is => 'ro', isa => 'Int'); -has 'entry' => ( - is => 'rw', - isa => 'Maybe[Entry]', - traits => ['DoNotSerialize'] # due to cycyles -); -has 'author' => ( - is => 'rw', - isa => 'Maybe[Author]', - traits => ['DoNotSerialize'] # due to cycyles -); - -sub id { +sub entry { my $self = shift; - return "(" . $self->entry_id . "-" . $self->author->id . ")" - if defined $self->author; - return "(" . $self->entry_id . "-" . $self->author_id . ")"; + return $self->repo->entries_find(sub { $_->id == $self->entry_id }); } -sub validate { +sub id { my $self = shift; - if (defined $self->author and defined $self->entry) { - if ($self->author->id != $self->author_id) { - die "Label has been built wrongly author->id and author_id differs.\n" - . "label->author->id: " - . $self->author->id - . ", label->author_id: " - . $self->author_id; - } - if ($self->entry->id != $self->entry_id) { - die "Label has been built wrongly entry->id and entry_id differs.\n" - . "label->entry->id: " - . $self->entry->id - . ", label->entry_id: " - . $self->entry_id; - } - } - return 1; + return + "(" + . ($self->author_id || "undef") . "-" + . ($self->entry_id || "undef") . ")"; } sub equals { my $self = shift; my $obj = shift; - die "Comparing apples to peaches! " . ref($self) . " against " . ref($obj) - unless ref($self) eq ref($obj); - if ($self->entry and $self->author and $obj->entry and $obj->author) { - return $self->equals_obj($obj); - } return $self->equals_id($obj); } @@ -84,15 +51,6 @@ sub equals_id { return 1; } -sub equals_obj { - my $self = shift; - my $obj = shift; - die "Comparing apples to peaches!!" unless ref($self) eq ref($obj); - return if !$self->entry->equals($obj->entry); - return if !$self->author->equals($obj->author); - return 1; -} - no Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/BibSpace/Model/Backup.pm b/lib/BibSpace/Model/Backup.pm index cbbca8b..fe9261e 100644 --- a/lib/BibSpace/Model/Backup.pm +++ b/lib/BibSpace/Model/Backup.pm @@ -20,7 +20,7 @@ class_has 'date_format_pattern' => (is => 'ro', default => '%Y-%m-%d-%H-%M-%S'); has 'uuid' => (is => 'rw', isa => 'Str', default => sub { create_uuid_as_string(UUID_V4) }); has 'name' => (is => 'rw', isa => 'Str', default => 'normal'); -has 'type' => (is => 'rw', isa => 'Str', default => 'storable'); +has 'type' => (is => 'rw', isa => 'Str', default => 'json'); has 'filename' => (is => 'rw', isa => 'Maybe[Str]'); has 'dir' => (is => 'rw', isa => 'Maybe[Str]'); has 'allow_delete' => (is => 'rw', isa => 'Bool', default => 1); @@ -102,11 +102,10 @@ sub get_age { sub create { my $self = shift; my $name = shift; - my $type = shift // 'storable'; + my $type = shift // 'json'; - my $ext = '.dat'; - $ext = '.sql' if $type eq 'mysql'; - $ext = '.json' if $type eq 'json'; + my $ext = '.json'; + $ext = '.sql' if $type eq 'mysql'; my $uuid = create_uuid_as_string(UUID_V4); @@ -142,7 +141,6 @@ sub parse { my $type = shift @tokens; my $date = shift @tokens; - $date =~ s/\.dat//g; $date =~ s/\.sql//g; $date =~ s/\.json//g; diff --git a/lib/BibSpace/Model/Entry.pm b/lib/BibSpace/Model/Entry.pm index 2d64dad..355b5f2 100644 --- a/lib/BibSpace/Model/Entry.pm +++ b/lib/BibSpace/Model/Entry.pm @@ -30,13 +30,6 @@ with 'IEntity', 'ILabeled', 'IAuthored', 'IHavingException'; use BibSpace::Model::SerializableBase::EntrySerializableBase; extends 'EntrySerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - return EntrySerializableBase->meta->rebless_instance_back($copy)->TO_JSON; -} - has 'entry_type' => (is => 'rw', isa => 'Str', default => 'paper'); has 'bibtex_key' => (is => 'rw', isa => 'Maybe[Str]'); has '_bibtex_type' => @@ -233,11 +226,11 @@ sub discover_attachments { # $self->add_attachment( 'unknown', $_ ) for @discovery_other; } -=item is_visible +=item belongs_to_visible_author Entry is visible if at least one of its authors is visible =cut -sub is_visible { +sub belongs_to_visible_author { my $self = shift; my $visible_author = any { $_->is_visible } $self->get_authors; @@ -295,14 +288,11 @@ sub is_talk { sub matches_our_type { my $self = shift; my $oType = shift; - my $repo = shift; - - die "This method requires repo, sorry." unless $repo; # example: ourType = inproceedings # mathces bibtex types: inproceedings, incollection - my $mapping = $repo->types_find( + my $mapping = $self->repo->types_find( sub { ($_->our_type cmp $oType) == 0; } @@ -332,27 +322,24 @@ sub populate_from_bib { return if !$self->has_valid_bibtex; - if (defined $self->bib and $self->bib ne '') { - my $bibtex_entry = new Text::BibTeX::Entry(); - my $s = $bibtex_entry->parse_s($self->bib); + my $bibtex_entry = new Text::BibTeX::Entry(); + my $s = $bibtex_entry->parse_s($self->bib); - $self->bibtex_key($bibtex_entry->key); - my $year_str = $bibtex_entry->get('year'); - if (Scalar::Util::looks_like_number($year_str)) { - $self->year($year_str); - } + $self->bibtex_key($bibtex_entry->key); + my $year_str = $bibtex_entry->get('year'); + if (Scalar::Util::looks_like_number($year_str)) { + $self->year($year_str); + } - if ($bibtex_entry->exists('booktitle')) { - $self->title($bibtex_entry->get('booktitle')); - } - if ($bibtex_entry->exists('title')) { - $self->title($bibtex_entry->get('title')); - } - $self->abstract($bibtex_entry->get('abstract') || undef); - $self->_bibtex_type($bibtex_entry->type); - return 1; + if ($bibtex_entry->exists('booktitle')) { + $self->title($bibtex_entry->get('booktitle')); } - return; + if ($bibtex_entry->exists('title')) { + $self->title($bibtex_entry->get('title')); + } + $self->abstract($bibtex_entry->get('abstract') || undef); + $self->_bibtex_type($bibtex_entry->type); + return 1; } sub add_bibtex_field { @@ -497,29 +484,105 @@ sub regenerate_html { return 0; } +sub get_exceptions { + my $self = shift; + return $self->repo->exceptions_filter(sub { $_->entry_id == $self->id }); +} + +sub get_teams { + my $self = shift; + + my @exception_team_id = map { $_->team_id } $self->get_exceptions; + my @exception_teams = $self->repo->teams_filter( + sub { + my $t = $_; + return grep { $_ eq $t->id } @exception_team_id; + } + ); + + ## Important: this means that entry-teams = teams + exceptions! + my %final_teams = map { $_->id => $_ } @exception_teams; + + foreach my $author ($self->get_authors) { + + foreach my $team ($author->get_teams) { + my $joined = $author->joined_team($team); + my $left = $author->left_team($team); + + # entry has no year... strange but possible + if (!$self->year) { + $final_teams{$team->id} = $team; + } + elsif ($joined <= $self->year and ($left > $self->year or $left == 0)) { + $final_teams{$team->id} = $team; + } + } + } + return values %final_teams; +} + +sub get_labelings { + my $self = shift; + return $self->repo->labelings_filter(sub { $_->entry_id == $self->id }); +} + +sub get_tags_of_type { + my $self = shift; + my $tag_type = shift // 1; + return grep { $_->type == $tag_type } $self->get_tags; +} + +sub get_tags { + my $self = shift; + my $potentialType = shift; + die "use entry->get_tags_of_type instead" if defined $potentialType; + + my @tag_ids = map { $_->tag_id } $self->get_labelings; + return $self->repo->tags_filter( + sub { + my $t = $_; + return grep { $_ eq $t->id } @tag_ids; + } + ); +} + +sub has_tag { + my $self = shift; + my $tag = shift; + return + defined $self->repo->labelings_find( + sub { $_->entry_id == $self->id and $_->tag_id == $tag->id }); +} + +sub get_authorships { + my $self = shift; + return $self->repo->authorships_filter(sub { $_->entry_id == $self->id }); +} + +sub get_authors { + my $self = shift; + my @author_ids = map { $_->author_id } $self->get_authorships; + return $self->repo->authors_filter( + sub { + my $a = $_; + return grep { $_ eq $a->id } @author_ids; + } + ); +} + sub has_author { my $self = shift; my $author = shift; - my $authorship_to_find = Authorship->new( - author => $author, - entry => $self, + my $authorship_to_find = $self->repo->entityFactory->new_Authorship( author_id => $author->id, entry_id => $self->id ); - my $authorship - = $self->authorships_find(sub { $_->equals($authorship_to_find) }); + = $self->repo->authorships_find(sub { $_->equals($authorship_to_find) }); return defined $authorship; } -sub has_master_author { - my $self = shift; - my $author = shift; - - return $self->has_author($author->get_master); -} - sub author_names_from_bibtex { my $self = shift; @@ -548,32 +611,6 @@ sub author_names_from_bibtex { return @author_names; } -sub get_teams { - my $self = shift; - - my @exception_teams = map { $_->team } $self->get_exceptions; - - ## Important: this means that entry-teams = teams + exceptions! - my %final_teams = map { $_->id => $_ } @exception_teams; - - foreach my $author ($self->get_authors) { - - foreach my $team ($author->get_teams) { - my $joined = $author->joined_team($team); - my $left = $author->left_team($team); - - # entry has no year... strange but possible - if (!$self->year) { - $final_teams{$team->id} = $team; - } - elsif ($joined <= $self->year and ($left > $self->year or $left == 0)) { - $final_teams{$team->id} = $team; - } - } - } - return values %final_teams; -} - sub has_team { my $self = shift; my $team = shift; diff --git a/lib/BibSpace/Model/Exception.pm b/lib/BibSpace/Model/Exception.pm index a586445..98db269 100644 --- a/lib/BibSpace/Model/Exception.pm +++ b/lib/BibSpace/Model/Exception.pm @@ -12,29 +12,24 @@ with 'IRelation'; use BibSpace::Model::SerializableBase::ExceptionSerializableBase; extends 'ExceptionSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { +sub team { my $self = shift; - my $copy = $self->meta->clone_object($self); - return ExceptionSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; + return if not $self->team_id or $self->team_id < 1; + return $self->repo->teams_find(sub { $_->id == $self->team_id }); } -has 'entry' => ( - is => 'rw', - isa => 'Maybe[Entry]', - traits => ['DoNotSerialize'] # due to cycyles -); -has 'team' => ( - is => 'rw', - isa => 'Maybe[Team]', - traits => ['DoNotSerialize'] # due to cycyles -); +sub entry { + my $self = shift; + return if not $self->entry_id or $self->entry_id < 1; + return $self->repo->entries_find(sub { $_->id == $self->entry_id }); +} sub id { my $self = shift; - return "(" . $self->entry_id . "-" . $self->team->name . ")" - if defined $self->team; - return "(" . $self->entry_id . "-" . $self->team_id . ")"; + return + "(" + . ($self->entry_id || "undef") . "-" + . ($self->team_id || "undef") . ")"; } =item equals @@ -44,11 +39,6 @@ sub id { sub equals { my $self = shift; my $obj = shift; - die "Comparing apples to peaches! " . ref($self) . " against " . ref($obj) - unless ref($self) eq ref($obj); - if ($self->entry and $self->team and $obj->entry and $obj->team) { - return $self->equals_obj($obj); - } return $self->equals_id($obj); } @@ -62,16 +52,6 @@ sub equals_id { return 1; } -sub equals_obj { - my $self = shift; - my $obj = shift; - die "Comparing apples to peaches! " . ref($self) . " against " . ref($obj) - unless ref($self) eq ref($obj); - return if !$self->entry->equals($obj->entry); - return if !$self->team->equals($obj->team); - return 1; -} - no Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/BibSpace/Model/IAuthored.pm b/lib/BibSpace/Model/IAuthored.pm index 6573083..a1f360d 100644 --- a/lib/BibSpace/Model/IAuthored.pm +++ b/lib/BibSpace/Model/IAuthored.pm @@ -4,54 +4,20 @@ use namespace::autoclean; use BibSpace::Functions::Core qw( sort_publications ); use Moose::Role; -has 'authorships' => ( - is => 'rw', - isa => 'ArrayRef[Authorship]', - traits => ['Array', 'DoNotSerialize'], - default => sub { [] }, - handles => { - authorships_all => 'elements', - authorships_add => 'push', - authorships_count => 'count', - authorships_find => 'first', - authorships_find_index => 'first_index', - authorships_filter => 'grep', - authorships_delete => 'delete', - authorships_clear => 'clear', - }, -); - sub has_authorship { my ($self, $authorship) = @_; - my $au = $self->authorships_find(sub { $_->equals($authorship) }); + my $au = $self->repo->authorships_find(sub { $_->equals($authorship) }); return defined $au; } sub add_authorship { my ($self, $authorship) = @_; - if (!$self->has_authorship($authorship)) { - $self->authorships_add($authorship); - } + $self->repo->authorships_save($authorship); } sub remove_authorship { my ($self, $authorship) = @_; - my $index = $self->authorships_find_index(sub { $_->equals($authorship) }); - return if $index == -1; - return 1 if $self->authorships_delete($index); - return; -} - -sub get_authors { - my $self = shift; - return map { $_->author } $self->authorships_all; -} - -sub get_entries { - my $self = shift; - my @entries = map { $_->entry } $self->authorships_all; - @entries = sort_publications(@entries); - return @entries; + return $self->repo->authorships_delete($authorship); } 1; diff --git a/lib/BibSpace/Model/IEntity.pm b/lib/BibSpace/Model/IEntity.pm index 5deb958..1b288bd 100644 --- a/lib/BibSpace/Model/IEntity.pm +++ b/lib/BibSpace/Model/IEntity.pm @@ -1,50 +1,45 @@ package IEntity; use namespace::autoclean; -use BibSpace::Util::IntegerUidProvider; use Moose::Role; use MooseX::StrictConstructor; require BibSpace::Model::SerializableBase::IEntitySerializableBase; with 'IEntitySerializableBase'; -requires 'equals'; -requires 'TO_JSON'; - -sub _generateUIDEntry { - my $self = shift; - - if (defined $self->old_mysql_id and $self->old_mysql_id > 0) { - $self->idProvider->registerUID($self->old_mysql_id); - return $self->old_mysql_id; - } - return $self->idProvider->generateUID(); -} +# Responsibility for ID management is moved to the storage backend has 'preferences' => (is => 'ro', isa => 'Preferences', traits => ['DoNotSerialize']); has 'idProvider' => ( - is => 'ro', - does => 'IUidProvider', - required => 1, - traits => ['DoNotSerialize'], + is => 'rw', + does => 'Maybe[IUidProvider]', + default => undef, + traits => ['DoNotSerialize'], ); has 'old_mysql_id' => (is => 'ro', isa => 'Maybe[Int]', default => undef); +has 'id' => (is => 'rw', isa => 'Maybe[Int]', default => undef); -has 'id' => ( - is => 'ro', - isa => 'Int', - builder => '_generateUIDEntry', - lazy => 1, - init_arg => undef -); +requires 'equals'; + +has 'repo' => (is => 'ro', isa => 'FlatRepositoryFacade', required => 1); + +# Custm cloning method is required because the following construct does not copy fileds values +# my $clone = $self->meta->clone_object($self); +# LabelingSerializableBase->meta->rebless_instance_back($clone); +sub get_base { + my $self = shift; + my $base_class_name = ref($self) . "SerializableBase"; + Class::Load::load_class($base_class_name); + return $base_class_name->new(%$self); +} -# called after the default constructor -sub BUILD { +# Cast self to SerializableBase and serialize +sub TO_JSON { my $self = shift; - $self->id; # trigger lazy execution of idProvider + return $self->get_base->TO_JSON; } 1; diff --git a/lib/BibSpace/Model/IHavingException.pm b/lib/BibSpace/Model/IHavingException.pm index 33bbf51..daa30e7 100644 --- a/lib/BibSpace/Model/IHavingException.pm +++ b/lib/BibSpace/Model/IHavingException.pm @@ -3,48 +3,14 @@ package IHavingException; use namespace::autoclean; use Moose::Role; -has 'exceptions' => ( - is => 'rw', - isa => 'ArrayRef[Exception]', - traits => ['Array'], - default => sub { [] }, - handles => { - exceptions_all => 'elements', - exceptions_add => 'push', - exceptions_count => 'count', - exceptions_find => 'first', - exceptions_find_index => 'first_index', - exceptions_filter => 'grep', - exceptions_delete => 'delete', - exceptions_clear => 'clear', - }, -); - -sub has_exception { - my ($self, $exception) = @_; - my $idx = $self->exceptions_find_index(sub { $_->equals($exception) }); - return $idx >= 0; -} - sub add_exception { my ($self, $exception) = @_; - if (!$self->has_exception($exception)) { - $self->exceptions_add($exception); - } + $self->repo->exceptions_save($exception); } sub remove_exception { my ($self, $exception) = @_; - - my $index = $self->exceptions_find_index(sub { $_->equals($exception) }); - return if $index == -1; - return 1 if $self->exceptions_delete($index); - return; -} - -sub get_exceptions { - my $self = shift; - return $self->exceptions_all; + return $self->repo->exceptions_delete($exception); } 1; diff --git a/lib/BibSpace/Model/ILabeled.pm b/lib/BibSpace/Model/ILabeled.pm index 4f88ee2..8aa3e95 100644 --- a/lib/BibSpace/Model/ILabeled.pm +++ b/lib/BibSpace/Model/ILabeled.pm @@ -3,60 +3,19 @@ package ILabeled; use namespace::autoclean; use Moose::Role; -has 'labelings' => ( - is => 'rw', - isa => 'ArrayRef[Labeling]', - traits => ['Array', 'DoNotSerialize'], - default => sub { [] }, - handles => { - labelings_all => 'elements', - labelings_add => 'push', - labelings_count => 'count', - labelings_find => 'first', - labelings_find_index => 'first_index', - labelings_filter => 'grep', - labelings_delete => 'delete', - labelings_clear => 'clear', - }, -); - -sub has_tag { - my $self = shift; - my $tag = shift; - return defined $self->labelings_find(sub { $_->tag->equals($tag) }); -} - sub has_labeling { my ($self, $labeling) = @_; - my $idx = $self->labelings_find_index(sub { $_->equals($labeling) }); - return $idx >= 0; + return $self->repo->labelings_find(sub { $_->equals($labeling) }); } sub add_labeling { my ($self, $labeling) = @_; - if (!$self->has_labeling($labeling)) { - $self->labelings_add($labeling); - } + $self->repo->labelings_save($labeling); } sub remove_labeling { my ($self, $labeling) = @_; - - my $index = $self->labelings_find_index(sub { $_->equals($labeling) }); - return if $index == -1; - return 1 if $self->labelings_delete($index); - return; -} - -sub get_tags { - my $self = shift; - my $tag_type = shift; - - my @tags = map { $_->tag } $self->labelings_all; - if (defined $tag_type) { - return grep { $_->type == $tag_type } @tags; - } - return @tags; + return $self->repo->labelings_delete($labeling); } 1; diff --git a/lib/BibSpace/Model/IMembered.pm b/lib/BibSpace/Model/IMembered.pm index d68f4da..b2f493e 100644 --- a/lib/BibSpace/Model/IMembered.pm +++ b/lib/BibSpace/Model/IMembered.pm @@ -3,57 +3,19 @@ package IMembered; use namespace::autoclean; use Moose::Role; -has 'memberships' => ( - is => 'rw', - isa => 'ArrayRef[Membership]', - traits => ['Array', 'DoNotSerialize'], - default => sub { [] }, - handles => { - memberships_all => 'elements', - memberships_add => 'push', - memberships_count => 'count', - memberships_find => 'first', - memberships_find_index => 'first_index', - memberships_filter => 'grep', - memberships_delete => 'delete', - memberships_clear => 'clear', - }, -); - -sub get_teams { - my $self = shift; - return map { $_->team } $self->memberships_all; -} - -sub has_team { - my $self = shift; - my $team = shift; - return defined $self->memberships_find(sub { $_->team->equals($team) }); -} - sub has_membership { my ($self, $membership) = @_; - my $idx = $self->memberships_find_index(sub { $_->equals($membership) }); - return $idx >= 0; + return defined $self->repo->memberships_find(sub { $_->equals($membership) }); } sub add_membership { my ($self, $membership) = @_; - - if (!$self->has_membership($membership)) { - $self->memberships_add($membership); - } + $self->repo->memberships_save($membership); } sub remove_membership { my ($self, $membership) = @_; - - # $membership->validate; - - my $index = $self->memberships_find_index(sub { $_->equals($membership) }); - return if $index == -1; - return 1 if $self->memberships_delete($index); - return; + return $self->repo->memberships_delete($membership); } 1; diff --git a/lib/BibSpace/Model/IRelation.pm b/lib/BibSpace/Model/IRelation.pm index 1735e49..14bbf24 100644 --- a/lib/BibSpace/Model/IRelation.pm +++ b/lib/BibSpace/Model/IRelation.pm @@ -4,11 +4,27 @@ use Moose::Role; requires 'id'; requires 'equals'; +has 'repo' => ( + is => 'ro', + isa => 'FlatRepositoryFacade', + required => 1, + traits => ['DoNotSerialize'] +); -# called after the default constructor -sub BUILD { +# Custm cloning method is required because the following construct does not copy fileds values +# my $clone = $self->meta->clone_object($self); +# LabelingSerializableBase->meta->rebless_instance_back($clone); +sub get_base { + my $self = shift; + my $base_class_name = ref($self) . "SerializableBase"; + Class::Load::load_class($base_class_name); + return $base_class_name->new(%$self); +} + +# Cast self to SerializableBase and serialize +sub TO_JSON { my $self = shift; - $self->id; # trigger lazy execution of idProvider + return $self->get_base->TO_JSON; } 1; diff --git a/lib/BibSpace/Model/Labeling.pm b/lib/BibSpace/Model/Labeling.pm index 9d33a56..3cfa73d 100644 --- a/lib/BibSpace/Model/Labeling.pm +++ b/lib/BibSpace/Model/Labeling.pm @@ -6,71 +6,37 @@ use BibSpace::Model::Entry; use BibSpace::Model::Tag; use BibSpace::Model::IRelation; use Try::Tiny; +use Data::Dumper; +$Data::Dumper::Maxdepth = 2; use Moose; with 'IRelation'; use BibSpace::Model::SerializableBase::LabelingSerializableBase; extends 'LabelingSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { +sub tag { my $self = shift; - my $copy = $self->meta->clone_object($self); - return LabelingSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; + return if not $self->tag_id or $self->tag_id < 1; + return $self->repo->tags_find(sub { $_->id == $self->tag_id }); } -has 'entry' => ( - is => 'rw', - isa => 'Maybe[Entry]', - traits => ['DoNotSerialize'] # due to cycyles -); -has 'tag' => ( - is => 'rw', - isa => 'Maybe[Tag]', - traits => ['DoNotSerialize'] # due to cycyles -); - -sub id { +sub entry { my $self = shift; - return "(" . $self->entry_id . "-" . $self->tag->name . ")" - if defined $self->tag; - return "(" . $self->entry_id . "-" . $self->tag_id . ")"; + return if not $self->entry_id or $self->entry_id < 1; + return $self->repo->entries_find(sub { $_->id == $self->entry_id }); } -sub validate { +sub id { my $self = shift; - if (defined $self->entry and defined $self->tag) { - if ($self->entry->id != $self->entry_id) { - die "Label has been built wrongly entry->id and entry_id differs.\n" - . "label->entry->id: " - . $self->entry->id - . ", label->entry_id: " - . $self->entry_id; - } - if ($self->tag->id != $self->tag_id) { - die "Label has been built wrongly tag->id and tag_id differs.\n" - . "label->tag->id: " - . $self->tag->id - . ", label->tag_id: " - . $self->tag_id; - } - } - return 1; + return + "(" + . ($self->entry_id || "undef") . "-" + . ($self->tag_id || "undef") . ")"; } sub equals { my $self = shift; my $obj = shift; - - die "Comparing apples to peaches! " - . ref($self) - . " against " - . ref($obj) . "." - unless ref($self) eq ref($obj); - - if ($self->entry and $self->tag and $obj->entry and $obj->tag) { - return $self->equals_obj($obj); - } return $self->equals_id($obj); } @@ -82,14 +48,6 @@ sub equals_id { return 1; } -sub equals_obj { - my $self = shift; - my $obj = shift; - return if !$self->entry->equals($obj->entry); - return if !$self->tag->equals($obj->tag); - return 1; -} - no Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/BibSpace/Model/Membership.pm b/lib/BibSpace/Model/Membership.pm index 4921789..e6b2b62 100644 --- a/lib/BibSpace/Model/Membership.pm +++ b/lib/BibSpace/Model/Membership.pm @@ -11,56 +11,29 @@ with 'IRelation'; use BibSpace::Model::SerializableBase::MembershipSerializableBase; extends 'MembershipSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { +sub author { my $self = shift; - my $copy = $self->meta->clone_object($self); - return MembershipSerializableBase->meta->rebless_instance_back($copy) - ->TO_JSON; + return if not $self->author_id or $self->author_id < 1; + return $self->repo->authors_find(sub { $_->id == $self->author_id }); } -has 'team' => (is => 'rw', isa => 'Maybe[Team]', traits => ['DoNotSerialize']); - -has 'author' => - (is => 'rw', isa => 'Maybe[Author]', traits => ['DoNotSerialize']); - -sub id { +sub team { my $self = shift; - return "(" . $self->team->name . "-" . $self->author->uid . ")" - if defined $self->author and defined $self->team; - return "(" . $self->team_id . "-" . $self->author_id . ")"; + return if not $self->team_id or $self->team_id < 1; + return $self->repo->teams_find(sub { $_->id == $self->team_id }); } -sub validate { +sub id { my $self = shift; - if (defined $self->author and defined $self->team) { - if ($self->author->id != $self->author_id) { - die "Label has been built wrongly author->id and author_id differs.\n" - . "label->author->id: " - . $self->author->id - . ", label->author_id: " - . $self->author_id; - } - if ($self->team->id != $self->team_id) { - die "Label has been built wrongly team->id and team_id differs.\n" - . "label->team->id: " - . $self->team->id - . ", label->team_id: " - . $self->team_id; - } - } - return 1; + return + "(" + . ($self->author_id || "undef") . "-" + . ($self->team_id || "undef") . ")"; } sub equals { my $self = shift; my $obj = shift; - return if !defined $obj; - die "Comparing apples to peaches! " . ref($self) . " against " . ref($obj) - unless ref($self) eq ref($obj); - if ($self->team and $self->author and $obj->team and $obj->author) { - return $self->equals_obj($obj); - } return $self->equals_id($obj); } @@ -68,20 +41,13 @@ sub equals_id { my $self = shift; my $obj = shift; die "Comparing apples to peaches!!" unless ref($self) eq ref($obj); + return if not $self->team_id; + return if not $self->author_id; return if $self->team_id != $obj->team_id; return if $self->author_id != $obj->author_id; return 1; } -sub equals_obj { - my $self = shift; - my $obj = shift; - die "Comparing apples to peaches!!" unless ref($self) eq ref($obj); - return if !$self->team->equals($obj->team); - return if !$self->author->equals($obj->author); - return 1; -} - no Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/BibSpace/Model/SerializableBase/AuthorSerializableBase.pm b/lib/BibSpace/Model/SerializableBase/AuthorSerializableBase.pm index 5c1b0bb..7d8299f 100644 --- a/lib/BibSpace/Model/SerializableBase/AuthorSerializableBase.pm +++ b/lib/BibSpace/Model/SerializableBase/AuthorSerializableBase.pm @@ -3,10 +3,13 @@ package AuthorSerializableBase; use utf8; use v5.16; use Moose; +use MooseX::Privacy; require BibSpace::Model::SerializableBase::IEntitySerializableBase; with 'IEntitySerializableBase'; -has 'uid' => (is => 'rw', isa => 'Str', documentation => q{Author name}); +has 'uid' => + (is => 'rw', isa => 'Str', documentation => q{Author name (deprecated)}); +has 'name' => (is => 'rw', isa => 'Str', documentation => q{Author name}); has 'display' => ( is => 'rw', @@ -18,9 +21,20 @@ has 'display' => ( has 'master_id' => ( is => 'rw', isa => 'Maybe[Int]', + traits => [qw/Protected/], documentation => q{Id of author's master object} ); +# Read-Only getter for master_id +# It also initializes master_id field for newly inserted elements +sub get_master_id { + my $self = shift; + if (not $self->master_id or $self->master_id < 0) { + $self->master_id($self->id); + } + return $self->master_id; +} + no Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/BibSpace/Model/SerializableBase/BibSpaceDTO.pm b/lib/BibSpace/Model/SerializableBase/BibSpaceDTO.pm index f6062c6..f591459 100644 --- a/lib/BibSpace/Model/SerializableBase/BibSpaceDTO.pm +++ b/lib/BibSpace/Model/SerializableBase/BibSpaceDTO.pm @@ -6,7 +6,10 @@ package BibSpaceDTO; use utf8; use v5.16; use Moose; +use MooseX::ClassAttribute; use Try::Tiny; +use Data::Dumper; +$Data::Dumper::Maxdepth = 2; # Class attribute is not serialized has 'formatVersion' => (is => 'ro', isa => 'Str', default => '1'); @@ -15,7 +18,14 @@ has 'formatVersion' => (is => 'ro', isa => 'Str', default => '1'); # It is used for deserializing dateTime objects # Always set this parameter to a value that is default for serialization in class 'DateTime' # Currently, it is '%Y-%m-%dT%T' -has 'dateTimeFormat' => (is => 'rw', isa => 'Str', default => '%Y-%m-%dT%T'); +# The parameter is ro, becuse there are no means and need to change it currently + +# Commenting it out due to warning that this is replaced by the class_has version +# has 'dateTimeFormat' => (is => 'ro', isa => 'Str', default => '%Y-%m-%dT%T'); + +# duplicate as class atribute to make some functions purely static +class_has 'dateTimeFormat' => + (is => 'ro', isa => 'Str', default => '%Y-%m-%dT%T'); # The hash has form: 'ClassName' => Array[InstanceofClassName] has 'data' => ( @@ -36,12 +46,13 @@ has 'data' => ( ); # Converts BibSpaceDTO object into JSON string +# THis method changes types of objects from Labeling to LabelingSerializableBase sub toJSON { my $self = shift; use JSON -convert_blessed_universally; - my $json_obj = JSON->new->convert_blessed->utf8->pretty; - return $json_obj->encode($self); + return JSON->new->convert_blessed->utf8->pretty->encode($self) + ; # this method chnages self!! } # Static constructor method that creates BibSpaceDTO from LayeredRepo @@ -62,35 +73,27 @@ sub fromLayeredRepo { # Resulting DTO will hold rich objects (not SerializableBase) and thus # requires additionall data from the repository sub toLayeredRepo { - my $self = shift; - my $jsonString = shift; - my $repoUidProvider = shift - // die "Need to provide UID provider of destination repository"; - my $repoPreferences = shift - // die "Need to provide Preferences of destination repository"; + my $self = shift; + my $jsonString = shift; + my $repoFacade = shift + // die "Need to provide repoFacade of destination repository"; + my $repoPreferences = $repoFacade->lr->preferences; # This parses a shallow BibSpaceDTO my $parsedDTO = bless(JSON->new->decode($jsonString), 'BibSpaceDTO'); # Each array (type => array) holds hashes - # Hases need to be blessed to become objects + # Hashes need to be blessed to become objects # Json gives SerializableBase # Here, we bless into # This will hold rich objects constructed from data from shallow object my $dto = BibSpaceDTO->new('FormatVersion' => 1); - # print "###############\n"; - # use Data::Dumper; - # $Data::Dumper::Maxdepth = 2; - # Init containers for objects for my $className ($parsedDTO->keys) { $dto->set($className, []); } - # Reset ID providers for all classes - only once! - $repoUidProvider->reset; - # Fill containers with objects for my $className ($parsedDTO->keys) { my $arrayRef = $parsedDTO->data->{$className}; @@ -100,14 +103,10 @@ sub toLayeredRepo { # Load className and call constructor Class::Load::load_class($className); - # Rich objects require several additional objects in their constructor - # See IEntity.pm for details - my $idProvider = $repoUidProvider->get_provider($className); - # BlessedObj has only SerializableBase # Call normal constructor to create rich object - my $mooseObj = $self->_hashToMooseObject($className, $objHash, - {idProvider => $idProvider, preferences => $repoPreferences}); + my $mooseObj = BibSpaceDTO->_hashToMooseObject($className, $objHash, + {preferences => $repoPreferences, repo => $repoFacade}); push @{$dto->get($className)}, $mooseObj; } } @@ -125,7 +124,8 @@ sub _hashToMooseObject { my $mooseObj; try { # Try calling constructor that creates DateTime rich fields from string - $mooseObj = $className->new__DateTime_from_string($self->dateTimeFormat, + $mooseObj + = $className->new__DateTime_from_string(BibSpaceDTO->dateTimeFormat, (%hashObj, %hashArgs)); } catch { diff --git a/lib/BibSpace/Model/SerializableBase/UserSerializableBase.pm b/lib/BibSpace/Model/SerializableBase/UserSerializableBase.pm index 90b1875..0baad84 100644 --- a/lib/BibSpace/Model/SerializableBase/UserSerializableBase.pm +++ b/lib/BibSpace/Model/SerializableBase/UserSerializableBase.pm @@ -15,11 +15,14 @@ class_has 'user_rank' => (is => 'ro', default => 0); has 'login' => (is => 'rw', isa => 'Str', required => 1); has 'real_name' => (is => 'rw', isa => 'Str', default => "unnamed"); has 'email' => (is => 'rw', isa => 'Str', required => 1); -has 'rank' => (is => 'rw'); -has 'pass' => (is => 'rw', isa => 'Str'); -has 'pass2' => (is => 'rw', isa => 'Str', documentation => q{Salt}); -has 'pass3' => - (is => 'rw', isa => 'Maybe[Str]', documentation => q{Currently unused}); +has 'rank' => (is => 'rw', default => UserSerializableBase->user_rank); +has 'pass' => (is => 'rw', isa => 'Str'); +has 'pass2' => (is => 'rw', isa => 'Str', documentation => q{Salt}); +has 'pass3' => ( + is => 'rw', + isa => 'Maybe[Str]', + documentation => q{Last password forgot token} +); has 'master_id' => (is => 'rw', default => 0); has 'tennant_id' => (is => 'rw', default => 0); diff --git a/lib/BibSpace/Model/Tag.pm b/lib/BibSpace/Model/Tag.pm index 8075209..fb1efc4 100644 --- a/lib/BibSpace/Model/Tag.pm +++ b/lib/BibSpace/Model/Tag.pm @@ -14,13 +14,6 @@ with 'IEntity', 'ILabeled'; use BibSpace::Model::SerializableBase::TagSerializableBase; extends 'TagSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - return TagSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; -} - has 'tagtype' => (is => 'rw', isa => 'Maybe[TagType]', traits => ['DoNotSerialize'],); @@ -40,9 +33,21 @@ sub get_authors { return uniq @authors; } -sub get_entries { +sub get_labelings { my $self = shift; - return map { $_->entry } $self->labelings_all; + return $self->repo->labelings_filter(sub { $_->tag_id == $self->id }); +} + +sub get_entries { + my $self = shift; + my @entry_ids = map { $_->entry_id } $self->get_labelings; + + return $self->repo->entries_filter( + sub { + my $e = $_; + return grep { $_ eq $e->id } @entry_ids; + } + ); } no Moose; diff --git a/lib/BibSpace/Model/TagType.pm b/lib/BibSpace/Model/TagType.pm index 8546928..ff81473 100644 --- a/lib/BibSpace/Model/TagType.pm +++ b/lib/BibSpace/Model/TagType.pm @@ -10,16 +10,6 @@ with 'IEntity'; use BibSpace::Model::SerializableBase::TagTypeSerializableBase; extends 'TagTypeSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - return TagTypeSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; -} - -has 'name' => (is => 'rw', isa => 'Str'); -has 'comment' => (is => 'rw', isa => 'Maybe[Str]'); - sub equals { my $self = shift; my $obj = shift; diff --git a/lib/BibSpace/Model/Team.pm b/lib/BibSpace/Model/Team.pm index 573de0c..abecba7 100644 --- a/lib/BibSpace/Model/Team.pm +++ b/lib/BibSpace/Model/Team.pm @@ -15,16 +15,6 @@ with 'IEntity', 'IMembered', 'IHavingException'; use BibSpace::Model::SerializableBase::TeamSerializableBase; extends 'TeamSerializableBase'; -# Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - return TeamSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; -} - -has 'name' => (is => 'rw', isa => 'Str'); -has 'parent' => (is => 'rw'); - sub equals { my $self = shift; my $obj = shift; @@ -36,13 +26,28 @@ sub equals { sub can_be_deleted { my $self = shift; - return if $self->memberships_count > 0; + return if scalar $self->get_authors > 0; return 1; } -sub get_members { +sub get_memberships { my $self = shift; - return map { $_->author } $self->memberships_all; + return $self->repo->memberships_filter(sub { $_->team_id == $self->id }); +} + +sub get_authors { + my $self = shift; + my @author_ids = map { $_->author_id } $self->get_memberships; + return $self->repo->authors_filter( + sub { + my $a = $_; + return grep { $_ eq $a->id } @author_ids; + } + ); +} + +sub get_members { + return shift->get_authors; } sub get_membership_beginning { @@ -63,11 +68,6 @@ sub get_membership_end { return $author->left_team($self); } -sub get_authors { - my $self = shift; - return map { $_->author } $self->memberships_all; -} - sub tags { my $self = shift; my $type = shift // 1; @@ -75,18 +75,23 @@ sub tags { my @myTags; my @members = $self->get_authors; foreach my $author (@members) { - push @myTags, $author->get_tags($type); + push @myTags, $author->get_tags_of_type($type); } @myTags = uniq @myTags; return @myTags; } +sub get_exceptions { + my $self = shift; + return $self->repo->exceptions_filter(sub { $_->team_id == $self->id }); +} + sub get_entries { my $self = shift; - my @myEntries; - my @members = $self->get_authors; + my @myEntries = (); + my @members = $self->get_authors; foreach my $author (@members) { push @myEntries, $author->get_entries; } diff --git a/lib/BibSpace/Model/Type.pm b/lib/BibSpace/Model/Type.pm index 9451ee5..fee7139 100644 --- a/lib/BibSpace/Model/Type.pm +++ b/lib/BibSpace/Model/Type.pm @@ -11,25 +11,30 @@ use BibSpace::Model::SerializableBase::TypeSerializableBase; extends 'TypeSerializableBase'; # Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - -# The bibtexTypes array is not cloned by default, so this needs to be added as fix - $copy->bibtexTypes($self->bibtexTypes); - - # Why this is not copied automatically? - $copy->onLanding($self->onLanding); +# sub TO_JSON { +# my $self = shift; +# my $copy = $self->meta->clone_object($self); +# +# # The bibtexTypes array is not cloned by default, so this needs to be added as fix +# $copy->bibtexTypes($self->bibtexTypes); +# +# # Why this is not copied automatically? +# $copy->onLanding($self->onLanding); +# +# # Suprisingly, some types may miss the obligatory field, so we provide a default vaule +# if ($self->our_type) { +# $copy->our_type($self->our_type); +# } +# else { +# $copy->our_type('Unnamed'); +# } +# my $tsb_debug = TypeSerializableBase->meta->rebless_instance_back($copy); +# return $tsb_debug->TO_JSON; +# } -# Suprisingly, some types may miss the obligatory field, so we provide a default vaule - if ($self->our_type) { - $copy->our_type($self->our_type); - } - else { - $copy->our_type('Unnamed'); - } - my $tsb_debug = TypeSerializableBase->meta->rebless_instance_back($copy); - return $tsb_debug->TO_JSON; +sub id { + my $self = shift; + return "(" . $self->our_type . "-" . join($self->bibtexTypes_all, ',') . ")"; } sub equals { diff --git a/lib/BibSpace/Model/User.pm b/lib/BibSpace/Model/User.pm index 0c82831..9b5a26b 100644 --- a/lib/BibSpace/Model/User.pm +++ b/lib/BibSpace/Model/User.pm @@ -12,39 +12,22 @@ use MooseX::ClassAttribute; with 'IEntity'; use BibSpace::Model::SerializableBase::UserSerializableBase; extends 'UserSerializableBase'; - -# Cast self to SerializableBase and serialize -sub TO_JSON { - my $self = shift; - my $copy = $self->meta->clone_object($self); - return UserSerializableBase->meta->rebless_instance_back($copy)->TO_JSON; -} - use DateTime::Format::Strptime; use DateTime; my $dtPattern = DateTime::Format::Strptime->new(pattern => '%Y-%m-%d %H:%M:%S'); -class_has 'admin_rank' => (is => 'ro', default => 2); -class_has 'manager_rank' => (is => 'ro', default => 1); -class_has 'user_rank' => (is => 'ro', default => 0); - -has 'login' => (is => 'rw', isa => 'Str', required => 1); -has 'real_name' => (is => 'rw', isa => 'Str', default => "unnamed"); -has 'email' => (is => 'rw', isa => 'Str', required => 1); -has 'rank' => (is => 'rw', default => User->user_rank); - -# pass = user password -has 'pass' => (is => 'rw', isa => 'Str'); - -# pass2 = salt -has 'pass2' => (is => 'rw', isa => 'Str'); -has 'pass3' => (is => 'rw', isa => 'Maybe[Str]'); +sub get_forgot_pass_token { + shift->pass3; +} -# TODO: forgot_token is not a DB field! -has 'forgot_token' => (is => 'rw', isa => 'Maybe[Str]'); -has 'master_id' => (is => 'rw', default => 0); -has 'tennant_id' => (is => 'rw', default => 0); +sub set_forgot_pass_token { + shift->pass3(shift); +} +sub reset_forgot_token { + my ($self) = @_; + $self->pass3(undef); +} has 'last_login' => ( is => 'rw', isa => 'DateTime', @@ -132,8 +115,8 @@ sub record_logging_in { my $self = shift; $self->last_login( DateTime->now->set_time_zone($self->preferences->local_time_zone)); - } + no Moose; __PACKAGE__->meta->make_immutable; 1; diff --git a/lib/BibSpace/Repository/FlatRepository.pm b/lib/BibSpace/Repository/FlatRepository.pm new file mode 100644 index 0000000..078f1b9 --- /dev/null +++ b/lib/BibSpace/Repository/FlatRepository.pm @@ -0,0 +1,156 @@ +# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T15:07:35 +package FlatRepository; +use namespace::autoclean; +use Moose; +use MooseX::ClassAttribute; +use feature qw( state say ); +use MooseX::StrictConstructor; +use Try::Tiny; +use List::Util qw(first); +use Scalar::Util qw( refaddr ); + +use BibSpace::Util::IUidProvider; +use BibSpace::Repository::RepositoryLayer; +use BibSpace::Util::EntityFactory; + +has 'logger' => (is => 'ro', does => 'ILogger', required => 1); +has 'preferences' => (is => 'ro', isa => 'Preferences', required => 1); +has 'facade' => + (is => 'rw', isa => 'Maybe[FlatRepositoryFacade]', default => undef); + +sub BUILD { + my $self = shift; + my $e_factory = EntityFactory->new( + logger => $self->logger, + preferences => $self->preferences + ); + $self->e_factory($e_factory); + $self->layer->e_factory($e_factory); +} + +sub set_facade { + my $self = shift; + my $facade = shift; + $self->facade($facade); + $self->e_factory->facade($facade); + $self->layer->e_factory->facade($facade); +} + +# will be set in the post-construction routine BUILD +has 'e_factory' => (is => 'rw', isa => 'EntityFactory'); + +# layer_name => RepositoryLayer +has 'layer' => ( + is => 'ro', + isa => 'RepositoryLayer', + traits => ['DoNotSerialize'], + default => sub { {} } +); + +# static methods +class_has 'entities' => ( + is => 'ro', + isa => 'ArrayRef[Str]', + default => sub { + + # ORDER IS IMPORTANT!!! TAG MUST BE AFTER TAGTYPE - it references it N:1! + ['Author', 'Entry', 'TagType', 'Tag', 'Team', 'Type', 'User']; + }, + traits => ['Array'], + handles => {get_entities => 'elements',}, +); +class_has 'relations' => ( + is => 'ro', + isa => 'ArrayRef[Str]', + default => sub { + ['Authorship', 'Exception', 'Labeling', 'Membership']; + }, + traits => ['Array'], + handles => {get_relations => 'elements',}, +); + +class_has 'models' => ( + is => 'ro', + isa => 'ArrayRef[Str]', + default => sub { + return [FlatRepository->get_entities, FlatRepository->get_relations]; + }, + traits => ['Array'], + handles => {get_models => 'elements',}, +); + +=item get_layer + Returns the only layer +=cut + +sub get_layer { + my $self = shift; + return $self->layer; +} + +# For compatibility with LayeredRespository interface +sub get_all_layers { + my $self = shift; + return ($self->get_layer); +} + +# For compatibility with LayeredRespository interface +sub get_read_layer { + my $self = shift; + return ($self->get_layer); +} + +sub get_summary_table { + my $self = shift; + return $self->get_layer->get_summary_table; +} + +sub all { + my $self = shift; + my $type = shift; + return $self->get_layer->all($type); +} + +sub count { + my ($self, $type) = @_; + return $self->get_layer->count($type); +} + +sub empty { + my ($self, $type) = @_; + return $self->get_layer->empty($type); +} + +sub exists { + my ($self, $type, $obj) = @_; + return $self->get_layer->exists($type, $obj); +} + +sub save { + my ($self, $type, @objects) = @_; + return $self->get_layer->save($type, @objects); +} + +sub update { + my ($self, $type, @objects) = @_; + return $self->get_layer->update($type, @objects); +} + +sub delete { + my ($self, $type, @objects) = @_; + return $self->get_layer->delete($type, @objects); +} + +sub filter { + my ($self, $type, $coderef) = @_; + return $self->get_layer->filter($type, $coderef); +} + +sub find { + my ($self, $type, $coderef) = @_; + return $self->get_layer->find($type, $coderef); +} + +__PACKAGE__->meta->make_immutable; +no Moose; +1; diff --git a/lib/BibSpace/Repository/RepositoryFacade.pm b/lib/BibSpace/Repository/FlatRepositoryFacade.pm similarity index 87% rename from lib/BibSpace/Repository/RepositoryFacade.pm rename to lib/BibSpace/Repository/FlatRepositoryFacade.pm index 973e2bb..03db87c 100644 --- a/lib/BibSpace/Repository/RepositoryFacade.pm +++ b/lib/BibSpace/Repository/FlatRepositoryFacade.pm @@ -1,11 +1,26 @@ -package RepositoryFacade; +package FlatRepositoryFacade; use namespace::autoclean; use Moose; use MooseX::ClassAttribute; use Try::Tiny; -has 'lr' => (is => 'ro', isa => 'LayeredRepository', required => 1); +has 'lr' => (is => 'ro', isa => 'FlatRepository', required => 1); + +# Repeat functions from lower layers +sub entityFactory { + shift->lr->e_factory; +} + +sub e_factory { + shift->lr->e_factory; +} + +# Inject facade into layer +sub BUILD { + my $self = shift; + $self->lr->set_facade($self); +} # static methods class_has 'entities' => ( @@ -34,64 +49,50 @@ class_has 'models' => ( is => 'ro', isa => 'ArrayRef[Str]', default => sub { - return [RepositoryFacade->get_entities, RepositoryFacade->get_relations]; + return [FlatRepositoryFacade->get_entities, + FlatRepositoryFacade->get_relations]; }, traits => ['Array'], handles => {get_models => 'elements',}, ); -sub hardReset { - my $self = shift; - return $self->lr->hardReset; -} - -sub _getIdProvider { - my $self = shift; - my $type = shift; - return $self->lr->uidProvider->get_provider($type); -} - #<<< no perltidy here sub authors_all { my ($self, @params) = @_; return $self->lr->all('Author', @params); } sub authors_count { my ($self, @params) = @_; return $self->lr->count('Author', @params); } sub authors_empty { my ($self, @params) = @_; return $self->lr->empty('Author', @params); } -sub authors_exists { my ($self, @params) = @_; return $self->lr->exists('Author', @params); } sub authors_save { my ($self, @params) = @_; return $self->lr->save('Author', @params); } sub authors_update { my ($self, @params) = @_; return $self->lr->update('Author', @params); } sub authors_delete { my ($self, @params) = @_; return $self->lr->delete('Author', @params); } sub authors_filter { my ($self, @params) = @_; return $self->lr->filter('Author', @params); } sub authors_find { my ($self, @params) = @_; return $self->lr->find('Author', @params); } -#>>> +#>>> #<<< no perltidy here sub authorships_all { my ($self, @params) = @_; return $self->lr->all('Authorship', @params); } sub authorships_count { my ($self, @params) = @_; return $self->lr->count('Authorship', @params); } sub authorships_empty { my ($self, @params) = @_; return $self->lr->empty('Authorship', @params); } -sub authorships_exists { my ($self, @params) = @_; return $self->lr->exists('Authorship', @params); } sub authorships_save { my ($self, @params) = @_; return $self->lr->save('Authorship', @params); } sub authorships_update { my ($self, @params) = @_; return $self->lr->update('Authorship', @params); } sub authorships_delete { my ($self, @params) = @_; return $self->lr->delete('Authorship', @params); } sub authorships_filter { my ($self, @params) = @_; return $self->lr->filter('Authorship', @params); } sub authorships_find { my ($self, @params) = @_; return $self->lr->find('Authorship', @params); } -#>>> +#>>> #<<< no perltidy here sub entries_all { my ($self, @params) = @_; return $self->lr->all('Entry', @params); } sub entries_count { my ($self, @params) = @_; return $self->lr->count('Entry', @params); } sub entries_empty { my ($self, @params) = @_; return $self->lr->empty('Entry', @params); } -sub entries_exists { my ($self, @params) = @_; return $self->lr->exists('Entry', @params); } sub entries_save { my ($self, @params) = @_; return $self->lr->save('Entry', @params); } sub entries_update { my ($self, @params) = @_; return $self->lr->update('Entry', @params); } sub entries_delete { my ($self, @params) = @_; return $self->lr->delete('Entry', @params); } sub entries_filter { my ($self, @params) = @_; return $self->lr->filter('Entry', @params); } sub entries_find { my ($self, @params) = @_; return $self->lr->find('Entry', @params); } -#>>> +#>>> #<<< no perltidy here sub exceptions_all { my ($self, @params) = @_; return $self->lr->all('Exception', @params); } sub exceptions_count { my ($self, @params) = @_; return $self->lr->count('Exception', @params); } sub exceptions_empty { my ($self, @params) = @_; return $self->lr->empty('Exception', @params); } -sub exceptions_exists { my ($self, @params) = @_; return $self->lr->exists('Exception', @params); } sub exceptions_save { my ($self, @params) = @_; return $self->lr->save('Exception', @params); } sub exceptions_update { my ($self, @params) = @_; return $self->lr->update('Exception', @params); } sub exceptions_delete { my ($self, @params) = @_; return $self->lr->delete('Exception', @params); } @@ -103,7 +104,6 @@ sub exceptions_find { my ($self, @params) = @_; return $self->lr->find('Except sub labelings_all { my ($self, @params) = @_; return $self->lr->all('Labeling', @params); } sub labelings_count { my ($self, @params) = @_; return $self->lr->count('Labeling', @params); } sub labelings_empty { my ($self, @params) = @_; return $self->lr->empty('Labeling', @params); } -sub labelings_exists { my ($self, @params) = @_; return $self->lr->exists('Labeling', @params); } sub labelings_save { my ($self, @params) = @_; return $self->lr->save('Labeling', @params); } sub labelings_update { my ($self, @params) = @_; return $self->lr->update('Labeling', @params); } sub labelings_delete { my ($self, @params) = @_; return $self->lr->delete('Labeling', @params); } @@ -115,7 +115,6 @@ sub labelings_find { my ($self, @params) = @_; return $self->lr->find('Labelin sub memberships_all { my ($self, @params) = @_; return $self->lr->all('Membership', @params); } sub memberships_count { my ($self, @params) = @_; return $self->lr->count('Membership', @params); } sub memberships_empty { my ($self, @params) = @_; return $self->lr->empty('Membership', @params); } -sub memberships_exists { my ($self, @params) = @_; return $self->lr->exists('Membership', @params); } sub memberships_save { my ($self, @params) = @_; return $self->lr->save('Membership', @params); } sub memberships_update { my ($self, @params) = @_; return $self->lr->update('Membership', @params); } sub memberships_delete { my ($self, @params) = @_; return $self->lr->delete('Membership', @params); } @@ -127,7 +126,6 @@ sub memberships_find { my ($self, @params) = @_; return $self->lr->find('Membe sub tags_all { my ($self, @params) = @_; return $self->lr->all('Tag', @params); } sub tags_count { my ($self, @params) = @_; return $self->lr->count('Tag', @params); } sub tags_empty { my ($self, @params) = @_; return $self->lr->empty('Tag', @params); } -sub tags_exists { my ($self, @params) = @_; return $self->lr->exists('Tag', @params); } sub tags_save { my ($self, @params) = @_; return $self->lr->save('Tag', @params); } sub tags_update { my ($self, @params) = @_; return $self->lr->update('Tag', @params); } sub tags_delete { my ($self, @params) = @_; return $self->lr->delete('Tag', @params); } @@ -139,7 +137,6 @@ sub tags_find { my ($self, @params) = @_; return $self->lr->find('Tag', @param sub tagTypes_all { my ($self, @params) = @_; return $self->lr->all('TagType', @params); } sub tagTypes_count { my ($self, @params) = @_; return $self->lr->count('TagType', @params); } sub tagTypes_empty { my ($self, @params) = @_; return $self->lr->empty('TagType', @params); } -sub tagTypes_exists { my ($self, @params) = @_; return $self->lr->exists('TagType', @params); } sub tagTypes_save { my ($self, @params) = @_; return $self->lr->save('TagType', @params); } sub tagTypes_update { my ($self, @params) = @_; return $self->lr->update('TagType', @params); } sub tagTypes_delete { my ($self, @params) = @_; return $self->lr->delete('TagType', @params); } @@ -151,7 +148,6 @@ sub tagTypes_find { my ($self, @params) = @_; return $self->lr->find('TagType' sub teams_all { my ($self, @params) = @_; return $self->lr->all('Team', @params); } sub teams_count { my ($self, @params) = @_; return $self->lr->count('Team', @params); } sub teams_empty { my ($self, @params) = @_; return $self->lr->empty('Team', @params); } -sub teams_exists { my ($self, @params) = @_; return $self->lr->exists('Team', @params); } sub teams_save { my ($self, @params) = @_; return $self->lr->save('Team', @params); } sub teams_update { my ($self, @params) = @_; return $self->lr->update('Team', @params); } sub teams_delete { my ($self, @params) = @_; return $self->lr->delete('Team', @params); } @@ -163,7 +159,6 @@ sub teams_find { my ($self, @params) = @_; return $self->lr->find('Team', @par sub types_all { my ($self, @params) = @_; return $self->lr->all('Type', @params); } sub types_count { my ($self, @params) = @_; return $self->lr->count('Type', @params); } sub types_empty { my ($self, @params) = @_; return $self->lr->empty('Type', @params); } -sub types_exists { my ($self, @params) = @_; return $self->lr->exists('Type', @params); } sub types_save { my ($self, @params) = @_; return $self->lr->save('Type', @params); } sub types_update { my ($self, @params) = @_; return $self->lr->update('Type', @params); } sub types_delete { my ($self, @params) = @_; return $self->lr->delete('Type', @params); } @@ -175,7 +170,6 @@ sub types_find { my ($self, @params) = @_; return $self->lr->find('Type', @par sub users_all { my ($self, @params) = @_; return $self->lr->all('User', @params); } sub users_count { my ($self, @params) = @_; return $self->lr->count('User', @params); } sub users_empty { my ($self, @params) = @_; return $self->lr->empty('User', @params); } -sub users_exists { my ($self, @params) = @_; return $self->lr->exists('User', @params); } sub users_save { my ($self, @params) = @_; return $self->lr->save('User', @params); } sub users_update { my ($self, @params) = @_; return $self->lr->update('User', @params); } sub users_delete { my ($self, @params) = @_; return $self->lr->delete('User', @params); } diff --git a/lib/BibSpace/Repository/LayeredRepository.pm b/lib/BibSpace/Repository/LayeredRepository.pm deleted file mode 100644 index b0f77ed..0000000 --- a/lib/BibSpace/Repository/LayeredRepository.pm +++ /dev/null @@ -1,429 +0,0 @@ -# This code was auto-generated using ArchitectureGenerator.pl on 2017-01-15T15:07:35 -package LayeredRepository; -use namespace::autoclean; -use Moose; -use MooseX::ClassAttribute; -use feature qw( state say ); -use MooseX::StrictConstructor; -use Try::Tiny; -use List::Util qw(first); -use Scalar::Util qw( refaddr ); - -use BibSpace::Util::IUidProvider; -use BibSpace::Util::SmartUidProvider; -use BibSpace::Repository::RepositoryLayer; -use BibSpace::Util::EntityFactory; - -# logic of the layered repository = read from one layer, write to all layers - -has 'logger' => (is => 'ro', does => 'ILogger', required => 1); -has 'preferences' => (is => 'ro', isa => 'Preferences', required => 1); -has 'id_provider_class' => (is => 'ro', isa => 'Str', required => 1); - -sub BUILD { - my $self = shift; - - my $uidP = SmartUidProvider->new( - logger => $self->logger, - idProviderClassName => $self->id_provider_class - ); - $self->uidProvider($uidP); - - my $e_factory = EntityFactory->new( - logger => $self->logger, - id_provider => $self->uidProvider, - preferences => $self->preferences - ); - $self->e_factory($e_factory); -} - -# will be set in the post-construction routine BUILD -has 'uidProvider' => (is => 'rw', isa => 'SmartUidProvider'); - -# will be set in the post-construction routine BUILD -has 'e_factory' => (is => 'rw', isa => 'EntityFactory'); - -# layer_name => RepositoryLayer -has 'layers' => ( - is => 'ro', - isa => 'HashRef[RepositoryLayer]', - traits => ['DoNotSerialize'], - default => sub { {} } -); - -# static methods -class_has 'entities' => ( - is => 'ro', - isa => 'ArrayRef[Str]', - default => sub { - - # ORDER IS IMPORTANT!!! TAG MUST BE AFTER TAGTYPE - it references it N:1! - ['Author', 'Entry', 'TagType', 'Tag', 'Team', 'Type', 'User']; - }, - traits => ['Array'], - handles => {get_entities => 'elements',}, -); -class_has 'relations' => ( - is => 'ro', - isa => 'ArrayRef[Str]', - default => sub { - ['Authorship', 'Exception', 'Labeling', 'Membership']; - }, - traits => ['Array'], - handles => {get_relations => 'elements',}, -); - -class_has 'models' => ( - is => 'ro', - isa => 'ArrayRef[Str]', - default => sub { - return [LayeredRepository->get_entities, LayeredRepository->get_relations]; - }, - traits => ['Array'], - handles => {get_models => 'elements',}, -); - -=item get_read_layer - Returns layer designated for reading. Throws exception if no read layer found -=cut - -sub get_read_layer { - my $self = shift; - my $readLayer = first { $_->is_read } $self->get_all_layers; - return $readLayer; -} - -=item get_all_layers - Returns all layers -=cut - -sub get_all_layers { - my $self = shift; - my @sorted_layers - = sort { $a->priority <=> $b->priority } values %{$self->layers}; - return @sorted_layers; -} - -=item get_layer - Searches for layer named $name in the repo and returs it. -=cut - -sub get_layer { - my $self = shift; - my $name = shift; - if (exists $self->layers->{$name}) { - return $self->layers->{$name}; - } - return; -} - -=item replace_layer - Replaces layer named $name in the repo with an input_layer object (e.g., from backup). - Sets id providers (no copy, replace!) in all layers to the id provider of the input layer. -=cut - -sub replace_layer { - my $self = shift; - my $name = shift; - my $input_layer = shift; - - my $destLayer = $self->get_layer($name); - if (ref($destLayer) ne ref($input_layer)) { - $self->logger->error( - "Replacing layers with of different type, this will lead to a failure!"); - die "Replacing layers with of different type, this will lead to a failure!"; - } - if ($destLayer and $input_layer->is_read != $destLayer->is_read) { - $self->logger->error( - "Replacing layers with different is_read value! This is experimental!"); - } - delete $self->layers->{$name}; - $self->layers->{$name} = $input_layer; - - ## START TRANSACTION - you really need to do all of this together - # $self->logger->debug("Replacing ID PROVIDER for all layers!"); - $self->replace_uid_provider($input_layer->uidProvider); - - # $self->logger->debug("Replacing E_FACTORY for all layers!"); - # e_factory has also id_providers, so it must be replaced - $self->replace_e_factory($input_layer->e_factory); - $self->e_factory->id_provider($input_layer->uidProvider); - ## COMMIT TRANSACTION -} - -=item replace_uid_provider - Replaces main id provider (loacted in $self->uidProvider) state. - This id provider is referenced by all layers! -=cut - -sub replace_uid_provider { - my $self = shift; - my $input_id_provider = shift; - $self->uidProvider($input_id_provider); - foreach my $layer ($self->get_all_layers) { - $layer->uidProvider($input_id_provider); - } -} - -=item replace_uid_provider - Replaces main id provider (loacted in $self->uidProvider) state. - This id provider is referenced by all layers! -=cut - -sub replace_e_factory { - my $self = shift; - my $input_e_factory = shift; - $self->e_factory($input_e_factory); - foreach my $layer ($self->get_all_layers) { - $layer->e_factory($input_e_factory); - } -} - -=item add_layer - Adds new layer to the layered repository. - Sets id provider (no copy, replace!) to this layer -=cut - -sub add_layer { - my $self = shift; - my $layer = shift; - - if (exists $self->layers->{$layer->name}) { - die "Layer with such name already exist."; - } - if ($layer->is_read and $self->get_read_layer) { - die "There can be only one read layer."; - } - $layer->e_factory($self->e_factory); - $layer->uidProvider($self->uidProvider); - $self->layers->{$layer->name} = $layer; -} - -=item reset_uid_providers - Resets main id provider (loacted in $self->uidProvider) state. - This id provider is referenced by all layers! - You reset here, and all id_provider references in the layers will be reset as well. -=cut - -sub reset_uid_providers { - my $self = shift; - - # $self->uidProvider is a container that is referenced directly by all layers! - $self->uidProvider->reset; -} - -sub get_summary_table { - my $self = shift; - my $str = "\n"; - - # get_id_provider_summary_hash - - my %count_hash; #layer_name => summary_hash - my @prefixes = qw(CNT_ ID_); - my @column_name = map { $_ . "OK" } @prefixes; - foreach my $layer ($self->get_all_layers) { - push @column_name, "CNT_" . $layer->name; - push @column_name, "ID_" . $layer->name; - $count_hash{"CNT_" . $layer->name} = $layer->get_summary_hash; - $count_hash{"ID_" . $layer->name} = $layer->get_id_provider_summary_hash; - } - - my $tab_width = 91; - - # calc CHECK status - foreach my $entity (LayeredRepository->get_models) { - foreach my $prefix (@prefixes) { - $count_hash{$prefix . 'OK'}->{$entity} = 'y'; - my $val; - foreach my $ln (reverse sort map { $_->name } $self->get_all_layers) { - if (!defined $val) { - $val = "" . $count_hash{$prefix . $ln}->{$entity}; - } - if ($count_hash{$prefix . $ln}->{$entity} ne $val) { - $count_hash{$prefix . 'OK'}->{$entity} = 'NO'; - } - } - } - } - - # print column names - for (1 .. $tab_width) { $str .= "-"; } - $str .= "\n"; - $str .= sprintf "| %-15s |", 'entity'; - foreach my $ln (reverse sort @column_name) { - $str .= sprintf " %-9s |", $ln; - } - $str .= "\n"; - for (1 .. $tab_width) { $str .= "-"; } - $str .= "\n"; - - # print data - foreach my $entity (LayeredRepository->get_entities) { - $str .= sprintf "| %-15s |", $entity; - foreach my $ln (reverse sort @column_name) { - $str .= sprintf " %9s |", $count_hash{$ln}->{$entity}; - } - $str .= "\n"; - } - for (1 .. $tab_width) { $str .= "-"; } - $str .= "\n"; - foreach my $entity (LayeredRepository->get_relations) { - $str .= sprintf "| %-15s |", $entity; - foreach my $ln (reverse sort @column_name) { - $str .= sprintf " %9s |", $count_hash{$ln}->{$entity}; - } - $str .= "\n"; - } - for (1 .. $tab_width) { $str .= "-"; } - $str .= "\n"; - $str - .= "IF YOU SEE ANY 'NO' IN ANY '_OK' COLUMN THEN MANIPULATING DATA IN THE SYSTEM MAY LEAD TO DATA LOSS OR EXCEPTIONS!"; - return $str; -} - -=item copy_data - Copies data between layers of repositories. - Does not change the uid_providers (there is one global) - Remember: If you move data FROM layer, which creates_on_read==true, - then you need to reset id_providers. -=cut - -# refactor this nicely into a single function - load dump fixture or so... -# IMPORTANT FIXME: this should be always called when entire dataset in smart array is replaced!! -# $self->app->repo->lr->get_read_layer->reset_data; -# $self->app->repo->lr->reset_uid_providers; -# $self->repo->lr->copy_data( { from => 'mysql', to => 'smart' } ); - -sub copy_data { - my $self = shift; - my $config = shift; - - my $backendFrom = $config->{from}; - my $backendTo = $config->{to}; - - my $srcLayer = $self->get_layer($backendFrom); - my $destLayer = $self->get_layer($backendTo); - - if ($srcLayer eq $destLayer) { - $self->logger->error( - "Source and destination layers are the same, cannot copy."); - return; - } - - if ((!$srcLayer) or (!$destLayer)) { - $self->logger->error( - "Cannot copy data from layer '$backendFrom' to layer '$backendTo' - one or more layers do not exist." - ); - return; - } - -# $self->logger->debug("State before reset_uid_providers: ".$self->get_summary_table); - - if ($srcLayer->creates_on_read) { - $self->logger->warn( - "Resetting all uid providers during copy from layer '$backendFrom' to layer '$backendTo'." - ); - $self->reset_uid_providers; - } - - # # Copy uid_provider from srcLayer to all layers - # # A) smart -> mysql - # # B) mysql -> smart - # $self->replace_uid_provider($srcLayer->uidProvider); - - # $self->logger->debug("State before reset data:".$self->get_summary_table); - ## avoid data duplication in the destination layer!! - $destLayer->reset_data - ; # this has unfortunately no meaning for mysql :( need to implement this - - # ALWAYS: first copy entities, then relations - - $self->logger->debug( - "Copying data from layer '$backendFrom' to layer '$backendTo'."); - - foreach my $type (LayeredRepository->get_entities) { - my @resultRead = $srcLayer->all($type); - my $resultSave = $destLayer->save($type, @resultRead); - - $self->logger->debug("'$backendFrom'-read " - . scalar(@resultRead) - . " objects '" - . $type - . "' ==> '$backendTo'-write $resultSave objects."); - - } - foreach my $type (LayeredRepository->get_relations) { - - my @resultRead = $srcLayer->all($type); - my $resultSave = $destLayer->save($type, @resultRead); - - $self->logger->debug("'$backendFrom'-read " - . scalar(@resultRead) - . " objects '" - . $type - . "' ==> '$backendTo'-write $resultSave objects."); - - } -} - -sub all { - my $self = shift; - my $type = shift; - return $self->get_read_layer->all($type); -} - -sub count { - my ($self, $type) = @_; - return $self->get_read_layer->count($type); -} - -sub empty { - my ($self, $type) = @_; - return $self->get_read_layer->empty($type); -} - -sub exists { - my ($self, $type, $obj) = @_; - return $self->get_read_layer->exists($type, $obj); -} - -sub save { - my ($self, $type, @objects) = @_; - my @return; - foreach my $layer ($self->get_all_layers) { - push @return, $layer->save($type, @objects); - } - return @return; -} - -sub update { - my ($self, $type, @objects) = @_; - my @return; - foreach my $layer ($self->get_all_layers) { - push @return, $layer->update($type, @objects); - } - return @return; -} - -sub delete { - my ($self, $type, @objects) = @_; - my @return; - foreach my $layer ($self->get_all_layers) { - push @return, $layer->delete($type, @objects); - } - return @return; -} - -sub filter { - my ($self, $type, $coderef) = @_; - return $self->get_read_layer->filter($type, $coderef); -} - -sub find { - my ($self, $type, $coderef) = @_; - return $self->get_read_layer->find($type, $coderef); -} - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/Repository/RepositoryLayer.pm b/lib/BibSpace/Repository/RepositoryLayer.pm index 250be39..4b29b7c 100644 --- a/lib/BibSpace/Repository/RepositoryLayer.pm +++ b/lib/BibSpace/Repository/RepositoryLayer.pm @@ -8,22 +8,15 @@ use MooseX::StrictConstructor; use Try::Tiny; use BibSpace::Util::IUidProvider; -use BibSpace::DAO::SmartArrayDAOFactory; use BibSpace::DAO::MySQLDAOFactory; -use BibSpace::DAO::RedisDAOFactory; -use BibSpace::Util::SmartUidProvider; use BibSpace::Util::EntityFactory; # this is rewritten during backup-restore, thus rw has 'e_factory' => (is => 'rw', isa => 'Maybe[EntityFactory]'); -# this is rewritten during backup-restore, thus rw -has 'uidProvider' => - (is => 'rw', isa => 'Maybe[SmartUidProvider]', default => undef); - =item backendFactoryName Stores the name of the DAO Factory for this layer. E.g. SmartArrayDaoFactory -=cut +=cut has 'backendFactoryName' => (is => 'ro', isa => 'Str', required => 1); has 'name' => (is => 'ro', isa => 'Str', required => 1); @@ -67,17 +60,17 @@ has 'creates_on_read' => ( required => 1, documentation => q{Does this layer creates new objects (BibSpaceEntity) on reading them from backend? - If yes, then moving data from this layer requires to reset all uid_providers. - Example 1: + If yes, then moving data from this layer requires to reset all uid_providers. + Example 1: 0) MySQL do creates objects on read, 1) Imagine moving data: MySQL -> overwrites -> SmartArray, 2) We reset data (only data) in SmartArray - it will be anyway overwritten, 3) MySQL created objects on read and registers new IDs in the id_provider, 4) There is one instance of id_provider (for each type) per all layers, - 5) The id_provider MUST BE RESET, because the objects already residing in the SmartArray + 5) The id_provider MUST BE RESET, because the objects already residing in the SmartArray would be duplicated with new IDs generated by the id_provider. Example 2: - 0) SmartArray does not create objects on read - it stores references and does not offer persistence. + 0) SmartArray does not create objects on read - it stores references and does not offer persistence. 1) Imagine moving data: SmartArray -> overwrites -> MySQL, 2) We reset data (only data) in MySQL - it will be anyway overwritten, 3) Nothing bad happens in MySQL, MySQL does not persist (directly) state of id_providers, @@ -89,7 +82,7 @@ has 'creates_on_read' => ( ); =item reset_data - Hard reset removes all instances of repositories and resets all id providers. + Hard reset removes all instances of repositories and resets all id providers. Use only for overwriting whole data set, e.g., during backup restore. =cut @@ -115,18 +108,7 @@ sub reset_data { } else { - try { - $self->handle->reset_data; - } - catch { - # Only SmartArray supports direct reset - if ( ref($self->handle) eq 'SmartArray' - or ref($self->handle) eq 'SmartHash') - { - $self->logger->error( - "Reset of " . ref($self->handle) . " failed. Error $_"); - } - }; + $self->handle->reset_data; } } @@ -136,44 +118,37 @@ sub daoDispatcher { my $entity_type = shift; if ($entity_type eq 'TagType') { - return $factory->getTagTypeDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getTagTypeDao(); } if ($entity_type eq 'Team') { - return $factory->getTeamDao($self->uidProvider->get_provider($entity_type)); + return $factory->getTeamDao(); } if ($entity_type eq 'Author') { - return $factory->getAuthorDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getAuthorDao(); } if ($entity_type eq 'Authorship') { - return $factory->getAuthorshipDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getAuthorshipDao(); } if ($entity_type eq 'Membership') { - return $factory->getMembershipDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getMembershipDao(); } if ($entity_type eq 'Entry') { - return $factory->getEntryDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getEntryDao(); } if ($entity_type eq 'Labeling') { - return $factory->getLabelingDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getLabelingDao(); } if ($entity_type eq 'Tag') { - return $factory->getTagDao($self->uidProvider->get_provider($entity_type)); + return $factory->getTagDao(); } if ($entity_type eq 'Exception') { - return $factory->getExceptionDao( - $self->uidProvider->get_provider($entity_type)); + return $factory->getExceptionDao(); } if ($entity_type eq 'Type') { - return $factory->getTypeDao($self->uidProvider->get_provider($entity_type)); + return $factory->getTypeDao(); } if ($entity_type eq 'User') { - return $factory->getUserDao($self->uidProvider->get_provider($entity_type)); + return $factory->getUserDao(); } $self->logger->error("Requested unknown entity_type: '$entity_type'"); die "Requested unknown entity_type: '$entity_type'"; @@ -200,45 +175,29 @@ sub getDao { sub get_summary_hash { my $self = shift; - my %hash = map { $_ => $self->count($_) } LayeredRepository->get_models; - return \%hash; -} - -=item get_id_provider_summary_hash - Provides a summary of a layer in form of a hash. - Has is build like this: entity_name => last_id of idProvider -=cut - -sub get_id_provider_summary_hash { - my $self = shift; - my %entities_hash - = map { $_ => $self->uidProvider->get_provider($_)->last_id } - LayeredRepository->get_entities; - my %relations_hash = map { $_ => '---' } LayeredRepository->get_relations; - my %hash = (%entities_hash, %relations_hash); + my %hash = map { $_ => $self->count($_) } FlatRepository->get_models; return \%hash; } =item get_summary_table - Prints nice summary table for all layers. + Prints nice summary table for all layers. Example: - ID_X = last id of the id_provider in layer X CNT_X = number of entities in layer X - ------------------------------------------- - | entity | ID_smart | CNT_mysql | - ------------------------------------------- - | Author | 1296 | 74 | - | Authorship | --- | 696 | - | Entry | 1117 | 379 | - | Exception | --- | 2 | - | Labeling | --- | 6 | - | Membership | --- | 27 | - | Tag | 231 | 50 | - | TagType | 4 | 4 | - | Team | 6 | 4 | - | Type | 17 | 24 | - | User | 1 | 1 | - ------------------------------------------- + ------------------------------- + | entity | CNT_mysql | + ------------------------------- + | Author | 74 | + | Authorship | 696 | + | Entry | 379 | + | Exception | 2 | + | Labeling | 6 | + | Membership | 27 | + | Tag | 50 | + | TagType | 4 | + | Team | 4 | + | Type | 24 | + | User | 1 | + ------------------------------- =cut sub get_summary_table { @@ -248,11 +207,9 @@ sub get_summary_table { my %count_hash; #layer_name => summary_hash my @layer_names; push @layer_names, "CNT_" . $self->name; - push @layer_names, "ID_" . $self->name; $count_hash{"CNT_" . $self->name} = $self->get_summary_hash; - $count_hash{"ID_" . $self->name} = $self->get_id_provider_summary_hash; - my $tab_width = 67; + my $tab_width = 31; for (1 .. $tab_width) { $str .= "_"; } $str .= "\n"; @@ -263,7 +220,7 @@ sub get_summary_table { $str .= "\n"; for (1 .. $tab_width) { $str .= "-"; } $str .= "\n"; - foreach my $entity (LayeredRepository->get_entities) { + foreach my $entity (FlatRepository->get_entities) { $str .= sprintf "| %-15s |", $entity; foreach my $ln (reverse sort @layer_names) { $str .= sprintf " %9s |", $count_hash{$ln}->{$entity}; @@ -272,7 +229,7 @@ sub get_summary_table { } for (1 .. $tab_width) { $str .= "-"; } $str .= "\n"; - foreach my $entity (sort LayeredRepository->get_relations) { + foreach my $entity (sort FlatRepository->get_relations) { $str .= sprintf "| %-15s |", $entity; foreach my $ln (reverse sort @layer_names) { $str .= sprintf " %9s |", $count_hash{$ln}->{$entity}; diff --git a/lib/BibSpace/TestManager.pm b/lib/BibSpace/TestManager.pm index f457f6e..364c284 100644 --- a/lib/BibSpace/TestManager.pm +++ b/lib/BibSpace/TestManager.pm @@ -4,7 +4,7 @@ use Try::Tiny; use BibSpace; use BibSpace::Model::Backup; -use BibSpace::Functions::BackupFunctions qw(restore_storable_backup); +use BibSpace::Functions::BackupFunctions qw(restore_json_backup); use BibSpace::Functions::FDB; # TODO: purge DB etc. use Moose; @@ -13,11 +13,11 @@ sub apply_fixture { my $self = shift; my $app = shift; ## THIS SHOULD BE REPEATED FOR EACH TEST! - my $fixture_file = $app->home->rel_file('fixture/bibspace_fixture.dat'); + my $fixture_file = $app->home->rel_file('fixture/bibspace_fixture.json'); my $fixture_name = '' . $fixture_file->basename; my $fixture_dir = '' . $fixture_file->dirname; my $fixture = Backup->new(dir => $fixture_dir, filename => $fixture_name); - restore_storable_backup($fixture, $app); + restore_json_backup($fixture, $app); } no Moose; diff --git a/lib/BibSpace/Util/DummyUidProvider.pm b/lib/BibSpace/Util/DummyUidProvider.pm deleted file mode 100644 index 064d984..0000000 --- a/lib/BibSpace/Util/DummyUidProvider.pm +++ /dev/null @@ -1,17 +0,0 @@ -package DummyUidProvider; -use Moose; -use BibSpace::Util::IUidProvider; -with 'IUidProvider'; -use List::Util qw(max); -use Scalar::Util qw( refaddr ); -use List::MoreUtils qw(any uniq); - -use feature qw(say); - -sub reset { 1; } -sub registerUID { 1; } -sub last_id { 1; } -sub generateUID { 1; } - -no Moose; -1; diff --git a/lib/BibSpace/Util/EntityFactory.pm b/lib/BibSpace/Util/EntityFactory.pm index 363f890..128746c 100644 --- a/lib/BibSpace/Util/EntityFactory.pm +++ b/lib/BibSpace/Util/EntityFactory.pm @@ -15,72 +15,77 @@ use BibSpace::Model::Entry; has 'logger' => (is => 'ro', does => 'ILogger', required => 1); # this may be rewritten during backup restore = must be rw (or writable in another way) -has 'id_provider' => (is => 'rw', isa => 'SmartUidProvider', required => 1); -has 'preferences' => (is => 'ro', isa => 'Preferences', required => 1); +has 'preferences' => (is => 'ro', isa => 'Preferences', required => 1); +has 'facade' => + (is => 'rw', isa => 'Maybe[FlatRepositoryFacade]', default => undef); sub new_Entry { my ($self, %args) = @_; - return Entry->new( - idProvider => $self->id_provider->get_provider('Entry'), - preferences => $self->preferences, - %args - ); + return Entry->new(preferences => $self->preferences, repo => $self->facade, + %args); } sub new_TagType { my ($self, %args) = @_; return TagType->new( - idProvider => $self->id_provider->get_provider('TagType'), preferences => $self->preferences, + repo => $self->facade, %args ); } sub new_Team { my ($self, %args) = @_; - return Team->new( - idProvider => $self->id_provider->get_provider('Team'), - preferences => $self->preferences, - %args - ); + return Team->new(preferences => $self->preferences, repo => $self->facade, + %args); } sub new_Author { my ($self, %args) = @_; return Author->new( - idProvider => $self->id_provider->get_provider('Author'), preferences => $self->preferences, + repo => $self->facade, %args ); } sub new_Tag { my ($self, %args) = @_; - return Tag->new( - idProvider => $self->id_provider->get_provider('Tag'), - preferences => $self->preferences, - %args - ); + return Tag->new(preferences => $self->preferences, repo => $self->facade, + %args); } sub new_Type { my ($self, %args) = @_; - return Type->new( - idProvider => $self->id_provider->get_provider('Type'), - preferences => $self->preferences, - %args - ); + return Type->new(preferences => $self->preferences, repo => $self->facade, + %args); } sub new_User { my ($self, %args) = @_; - return User->new( - idProvider => $self->id_provider->get_provider('User'), - preferences => $self->preferences, - %args - ); + return User->new(preferences => $self->preferences, repo => $self->facade, + %args); } +sub new_Authorship { + my ($self, %args) = @_; + return Authorship->new(repo => $self->facade, %args); +} + +sub new_Membership { + my ($self, %args) = @_; + return Membership->new(repo => $self->facade, %args); +} + +sub new_Labeling { + my ($self, %args) = @_; + return Labeling->new(repo => $self->facade, %args); +} + +sub new_Exception { + my ($self, %args) = @_; + return Exception->new(repo => $self->facade, %args); +} __PACKAGE__->meta->make_immutable; no Moose; 1; diff --git a/lib/BibSpace/Util/IntegerUidProvider.pm b/lib/BibSpace/Util/IntegerUidProvider.pm deleted file mode 100644 index 5d006e1..0000000 --- a/lib/BibSpace/Util/IntegerUidProvider.pm +++ /dev/null @@ -1,83 +0,0 @@ -package IntegerUidProvider; -use Moose; -use BibSpace::Util::IUidProvider; -with 'IUidProvider'; -use List::Util qw(max); -use Scalar::Util qw( refaddr ); -use List::MoreUtils qw(any uniq); -use feature qw(say); - -use BibSpace::Util::SimpleLogger; - -# -use MooseX::ClassAttribute; - -has 'data' => ( - traits => ['Hash'], - is => 'ro', - isa => 'HashRef[Int]', - default => sub { {} }, - handles => { - uid_set => 'set', - uid_get => 'get', - uid_has => 'exists', - uid_defined => 'defined', - uid_num => 'count', - uid_keys => 'keys', - uid_pairs => 'kv', - clear => 'clear', - }, -); - -sub reset { - my ($self) = @_; - $self->logger->warn( - "Resetting UID record for type '" . $self->for_type . "' !"); - $self->clear; -} - -sub registerUID { - my ($self, $uid) = @_; - - if (!$self->uid_defined($uid)) { - $self->uid_set($uid => 1); - $self->logger->lowdebug( - "Registered uid '$uid' for type '" . $self->for_type . "'."); - } - else { - my $msg - = "Cannot registerUID for type '" - . $self->for_type - . "'. It exists already! Wanted to reg: $uid."; - $self->logger->error($msg); - ### TODO: THIS SHOULD BE DIE!! - # warn is only for debugging - # die $msg; - } -} - -sub last_id { - my ($self) = @_; - my $curr_max = 0; # starting default id - my $curr_max_candidate = max $self->uid_keys; - if (defined $curr_max_candidate and $curr_max_candidate > 0) { - $curr_max = $curr_max_candidate; - } - return $curr_max; -} - -sub generateUID { - my ($self) = @_; - - my $curr_max = $self->last_id; - my $new_uid = $curr_max + 1; - $self->uid_set($new_uid => 1); - -# this debug msg can break a lot - generated uids are delayed and some older copies are returned... strange -# $self->logger->debug(" (".refaddr($self).") has generated uid '$new_uid' for type '".$self->for_type."'"); -# say " (".refaddr($self).") has generated uid '$new_uid' for type '".$self->for_type."'"; - return $new_uid; -} - -no Moose; -1; diff --git a/lib/BibSpace/Util/SmartUidProvider.pm b/lib/BibSpace/Util/SmartUidProvider.pm deleted file mode 100644 index 73487b2..0000000 --- a/lib/BibSpace/Util/SmartUidProvider.pm +++ /dev/null @@ -1,131 +0,0 @@ -package SmartUidProvider; - -use v5.16; -use Try::Tiny; -use Data::Dumper; -use namespace::autoclean; - -use List::Util qw(max); -use Scalar::Util qw( refaddr ); -use List::MoreUtils qw(any uniq); -use feature qw(say); - -use Moose; - -use Moose::Util::TypeConstraints; -use List::Util qw(first); -use List::MoreUtils qw(first_index); - -use MooseX::Storage; -with Storage(format => 'JSON', 'io' => 'File'); - -=item - This is a in-memory data structure (hash) to hold all objects of BibSpace. - It is build like this: - String "TypeName" => Array of Objects with type TypeName. - It could be improved for performance like this: - String "TypeName" => { Integer UID => Object with type TypeName}. -=cut - -has 'logger' => - (is => 'ro', does => 'ILogger', required => 1, traits => ['DoNotSerialize']); -has 'idProviderClassName' => (is => 'ro', isa => 'Str', required => 1); - -has 'data' => ( - traits => ['Hash'], - is => 'ro', - isa => 'HashRef[IUidProvider]', - default => sub { {} }, - handles => { - _set => 'set', - _get => 'get', - _has => 'exists', - _defined => 'defined', - _keys => 'keys', - _values => 'values', - _num => 'count', - _pairs => 'kv', - }, -); - -sub get_provider { - my ($self, $type) = @_; - $self->_init($type); - my $provider = $self->_get($type); - return $provider; -} - -sub _init { - my ($self, $type) = @_; - - if (!$type) { - $self->logger->error("_init requires a type!"); - die "_init requires a type!"; - } - if (!$self->_defined($type)) { - try { - my $className = $self->idProviderClassName; - Class::Load::load_class($className); - my $providerInstance - = $className->new(logger => $self->logger, for_type => $type); - $self->_set($type, $providerInstance); - } - catch { - my $msg - = "Requested unknown type of IUidProvider : '" - . $self->idProviderClassName - . "'. Error: $_"; - $self->logger->error($msg); - die $msg; - }; - } -} - -sub reset { - my $self = shift; - $self->logger->warn("Resetting all UID Providers"); - foreach my $type ($self->_keys) { - $self->_get($type)->clear; - } - -} - -sub registerUID { - my ($self, $type, $uid) = @_; - - if (!$self->_get($type)->uid_defined($uid)) { - $self->_get($type)->uid_set($uid => 1); - } - else { - my $msg - = "Cannot registerUID. It exists already! Wanted to reg: $uid. Existing: " - . join(' ', sort $self->_keys); - $self->logger->error($msg); - die $msg; - } -} - -sub last_id { - my ($self, $type) = @_; - my $curr_max = 1; # starting default id - my $curr_max_candidate = max $self->_get($type)->uid_keys; - if (defined $curr_max_candidate and $curr_max_candidate > 0) { - $curr_max = $curr_max_candidate; - } - return $curr_max; -} - -# I think this is never used... -sub generateUID { - my ($self, $type) = @_; - - my $curr_max = $self->last_id($type); - my $new_uid = $curr_max + 1; - $self->_get($type)->uid_set($new_uid => 1); - $self->logger->debug("Generated uid '$new_uid' for object type '$type'."); - return $new_uid; -} - -__PACKAGE__->meta->make_immutable; -no Moose; -1; diff --git a/lib/BibSpace/files/config/default.conf b/lib/BibSpace/files/config/default.conf index da1a8b9..b4dedbd 100644 --- a/lib/BibSpace/files/config/default.conf +++ b/lib/BibSpace/files/config/default.conf @@ -1,5 +1,5 @@ { - config_valid_with => 'v0.5.0', + config_valid_with => 'v0.6.0', backups_dir => app->home->rel_file('backups'), upload_dir => app->home->rel_file('public/uploads'), log_dir => app->home->rel_file('log'), @@ -15,10 +15,10 @@ db_pass => "passw00rd", cron_day_freq_lock => 0, - cron_night_freq_lock => 0, - cron_week_freq_lock => 0, + cron_night_freq_lock => 0, + cron_week_freq_lock => 0, cron_month_freq_lock => 0, - + demo_mode => 0, demo_msg => '', diff --git a/lib/BibSpace/files/templates/authors/authors.html.ep b/lib/BibSpace/files/templates/authors/authors.html.ep index 779d041..28f027f 100644 --- a/lib/BibSpace/files/templates/authors/authors.html.ep +++ b/lib/BibSpace/files/templates/authors/authors.html.ep @@ -11,7 +11,7 @@
  • % } - + All authors
  • @@ -22,7 +22,7 @@
  • % } - + Show visible
  • @@ -33,7 +33,7 @@
  • % } - + Show invisible
  • @@ -47,7 +47,7 @@ % else{ % foreach my $let (@{$letters}){ % if( $selected_letter and $let eq $selected_letter ){ @@ -56,7 +56,9 @@ % else{
  • % } - <%= $let %> + + <%= $let %> +
  • %} @@ -70,32 +72,51 @@ % foreach my $author (@{$authors}){ - + - + - - - - - + + + + + % $i++; % $j--; diff --git a/lib/BibSpace/files/templates/authors/edit_author.html.ep b/lib/BibSpace/files/templates/authors/edit_author.html.ep index 6300c65..ae717c8 100644 --- a/lib/BibSpace/files/templates/authors/edit_author.html.ep +++ b/lib/BibSpace/files/templates/authors/edit_author.html.ep @@ -10,7 +10,7 @@

    Author:

    -

    <%= $author->uid %>

    +

    <%= $author->uid %>

    @@ -20,23 +20,23 @@ @@ -47,44 +47,44 @@ - +

    Edit author

    @@ -120,7 +120,7 @@
    - + @@ -131,11 +131,11 @@
    - + - +
    - +
    @@ -151,29 +151,29 @@
    % my $i = 0; - % + %
    - +
    @@ -183,7 +183,7 @@
    - +
    @@ -218,9 +218,9 @@

    - - + +
    @@ -235,21 +235,27 @@
    @@ -300,10 +306,10 @@ %}

    [<%= $j %>]

    +

    <%= $j %>

    +
    % if ($author->is_visible){ - + + + %} %else{ - - + + + + %} % if ( $author->can_be_deleted){ - + + + %} %else{ %} - Edit + + Edit +

    <%= $author->id %>

    <%= $author->master %>

    Show papers Landing page Landing page years +

    + + <%= $author->id %> +

    +
    +

    + <%= $author->master->name %> +

    +
    Show papers Landing page Landing page years
    - +
    - <%= $author->joined_team( $team ) %> + + + <%= $author->joined_team( $team ) %> + - <%= $author->left_team( $team ) || b('∞') %> + + + <%= $author->left_team( $team ) || b('∞') %> +
    @@ -261,7 +267,7 @@
    - - Show papers of + + Show papers of
    - + + + - -
    @@ -317,7 +323,7 @@
    - <%= $team->{name} %> + <%= $team->{name} %> | @@ -325,7 +331,7 @@ % $jj++; % }
    - +
    @@ -339,9 +345,9 @@ %foreach my $tag (@$tags){
    - <%= $tag->{name} %> + <%= $tag->{name} %> - + <%= num_pubs_for_author_and_tag( $author, $tag )%> @@ -349,7 +355,7 @@ % $k++; %}
    - +
    @@ -358,9 +364,9 @@
    @@ -369,20 +375,20 @@
    - + Merge authors

    Merge authors (experimental feature)

    -

    The author, whose ID is given in the form below, will be merged into this one (<%= $author->{master} %>). Feature is experimental.

    +

    The author, whose ID is given in the form below, will be merged into this one (<%= $author->master->name %>). Feature is experimental.

    Merging affects only entries.

    The result is immediate. Make backup if in doubt. Some things can be reverted (but no teams!)

    Warning! The source author will abandon all teams.

    - +
    @@ -426,7 +432,7 @@ + diff --git a/lib/BibSpace/files/templates/backup/backup.html.ep b/lib/BibSpace/files/templates/backup/backup.html.ep index b12cdbb..c9829ba 100644 --- a/lib/BibSpace/files/templates/backup/backup.html.ep +++ b/lib/BibSpace/files/templates/backup/backup.html.ep @@ -8,11 +8,6 @@
    Back - %= form_for backup_do => (class=>'display-inline') => begin - - % end %= form_for backup_do_mysql => (class=>'display-inline') => begin % end % } - -
    @@ -87,10 +77,7 @@ - % if( $backup->is_healthy and $backup->type eq 'storable' ){ - Restore... - %} - % elsif( $backup->is_healthy and $backup->type eq 'json' ){ + % if( $backup->is_healthy and $backup->type eq 'json' ){ Restore... %} % else{ @@ -147,43 +134,7 @@
    - - - - - + - + %}
    diff --git a/lib/BibSpace/files/templates/display/danger_zone.html.ep b/lib/BibSpace/files/templates/display/danger_zone.html.ep index 5cb4e04..460d423 100644 --- a/lib/BibSpace/files/templates/display/danger_zone.html.ep +++ b/lib/BibSpace/files/templates/display/danger_zone.html.ep @@ -18,35 +18,13 @@
    -
    Move data between layers
    +
    Reset system state
    - -
    -
    Reset layers
    - -
    - - -
    -
    Generate random data
    - -
    - - - \ No newline at end of file + diff --git a/lib/BibSpace/files/templates/login/noregister.html.ep b/lib/BibSpace/files/templates/login/noregister.html.ep index 2a95c8e..fcbdc78 100644 --- a/lib/BibSpace/files/templates/login/noregister.html.ep +++ b/lib/BibSpace/files/templates/login/noregister.html.ep @@ -7,16 +7,15 @@
    - Registration is disabled -

    If you require an account, contact the system administrator.

    +

    Registration is disabled. If you require an account, contact the system administrator.


    - - + + diff --git a/lib/BibSpace/files/templates/navi.html.ep b/lib/BibSpace/files/templates/navi.html.ep index 717b7d2..fc39c82 100644 --- a/lib/BibSpace/files/templates/navi.html.ep +++ b/lib/BibSpace/files/templates/navi.html.ep @@ -54,14 +54,14 @@
  • - + Show papers <%= num_pubs('paper')%>
  • - + Show talks <%= num_pubs('talk')%> @@ -84,7 +84,7 @@
  • Show papers without month field
  • - +
  • % my @ttobjs = get_important_tag_types($self); % foreach my $ttobj (@ttobjs){ @@ -106,14 +106,14 @@
  • Add
  • - + Show all <%= num_authors()%>
  • - + Show visible <%= num_visible_authors()%> @@ -123,14 +123,14 @@
  • - % my @authors = sort {$a->master cmp $b->master} get_visible_authors(); + % my @authors = sort {$a->uid cmp $b->uid} get_visible_authors(); % foreach my $author (@authors){
  • - - <%= $author->{master} %> + + <%= $author->{uid} %> <%= num_pubs( undef, undef, [$author->get_entries] ) %> - +
  • @@ -156,19 +156,19 @@ % foreach my $team ( @all_teams ){
  • - <%= $team->name %> + <%= $team->name %> <%= scalar $team->get_members %> - +
  • %} - + @@ -181,14 +181,14 @@ - + @@ -68,7 +68,7 @@
    - + <%= $exception->team->name %> @@ -81,7 +81,7 @@ Detected teams will be ignored after adding any exception (at least should be...). But temporarily they are not. Teams = teams + exceptions. -
    + % } diff --git a/lib/BibSpace/files/templates/publications/manage_tags.html.ep b/lib/BibSpace/files/templates/publications/manage_tags.html.ep index 85cce5a..080e79c 100644 --- a/lib/BibSpace/files/templates/publications/manage_tags.html.ep +++ b/lib/BibSpace/files/templates/publications/manage_tags.html.ep @@ -9,7 +9,7 @@
    @@ -17,27 +17,27 @@
    - %= include 'preview_row', preview => $entry->{html}, btype=> $entry->{bibtex_type}, bkey => $entry->bibtex_key; + %= include 'preview_row', preview => $entry->html, btype=> $entry->bibtex_type, bkey => $entry->bibtex_key; % foreach my $ttobj ( @$tag_types ){
    - - - - - - diff --git a/lib/BibSpace/files/templates/publications/show_authors.html.ep b/lib/BibSpace/files/templates/publications/show_authors.html.ep index 8a38c35..a7e9061 100644 --- a/lib/BibSpace/files/templates/publications/show_authors.html.ep +++ b/lib/BibSpace/files/templates/publications/show_authors.html.ep @@ -30,9 +30,9 @@ % my $i = 0; % foreach my $author (@$authors){ - - <%= $author->{master} %> - + + <%= $author->master->name %> + % $i++; % } @@ -52,15 +52,15 @@ % foreach my $team (@$teams){ - <%= $team->{name} %> - + <%= $team->{name} %> + - % } + % }

    - + diff --git a/lib/BibSpace/files/templates/tags/authors_having_tag.html.ep b/lib/BibSpace/files/templates/tags/authors_having_tag.html.ep index 1106bff..2e3d8be 100644 --- a/lib/BibSpace/files/templates/tags/authors_having_tag.html.ep +++ b/lib/BibSpace/files/templates/tags/authors_having_tag.html.ep @@ -26,15 +26,15 @@ %foreach my $author (@$authors){
    - <%= $author->{master} %> + <%= $author->master->name %> - + <%=num_pubs_for_author_and_tag($author, $tag)%>
    %} - + diff --git a/lib/BibSpace/files/templates/tags/authors_having_tag_read.html.ep b/lib/BibSpace/files/templates/tags/authors_having_tag_read.html.ep index 82164e1..19a790b 100644 --- a/lib/BibSpace/files/templates/tags/authors_having_tag_read.html.ep +++ b/lib/BibSpace/files/templates/tags/authors_having_tag_read.html.ep @@ -1,8 +1,8 @@ % layout 'default'; diff --git a/lib/BibSpace/files/templates/tagtypes/edit.html.ep b/lib/BibSpace/files/templates/tagtypes/edit.html.ep index 659aff3..7338d47 100644 --- a/lib/BibSpace/files/templates/tagtypes/edit.html.ep +++ b/lib/BibSpace/files/templates/tagtypes/edit.html.ep @@ -17,12 +17,12 @@ - +
    - +
    @@ -31,7 +31,7 @@
    - +
    diff --git a/lib/BibSpace/files/templates/tagtypes/tagtypes.html.ep b/lib/BibSpace/files/templates/tagtypes/tagtypes.html.ep index a8177c9..a671643 100644 --- a/lib/BibSpace/files/templates/tagtypes/tagtypes.html.ep +++ b/lib/BibSpace/files/templates/tagtypes/tagtypes.html.ep @@ -7,11 +7,11 @@
    -
    +
    @@ -28,7 +28,7 @@
    - + % if($tt->id == 1 or $tt->id == 2){ @@ -40,7 +40,7 @@ % } - +
    @@ -54,7 +54,7 @@
    -
    \ No newline at end of file + diff --git a/lib/BibSpace/files/templates/types/add.html.ep b/lib/BibSpace/files/templates/types/add.html.ep index 0ac0205..5382cd6 100644 --- a/lib/BibSpace/files/templates/types/add.html.ep +++ b/lib/BibSpace/files/templates/types/add.html.ep @@ -5,7 +5,7 @@
    diff --git a/lib/BibSpace/files/templates/types/manage_types.html.ep b/lib/BibSpace/files/templates/types/manage_types.html.ep index b507bc3..39b1420 100644 --- a/lib/BibSpace/files/templates/types/manage_types.html.ep +++ b/lib/BibSpace/files/templates/types/manage_types.html.ep @@ -19,21 +19,22 @@ -
    +
    <%= $type->our_type %> = % foreach my $a_type_str (@assigned_btypes){
    - - <%= $a_type_str %> + + + <%= $a_type_str %> @@ -50,13 +51,13 @@
    - % + % % foreach my $b_type (@$unassigned_btypes){ @@ -78,7 +79,7 @@
    - +
    diff --git a/lib/BibSpace/files/templates/types/types.html.ep b/lib/BibSpace/files/templates/types/types.html.ep index 6904746..271a87d 100644 --- a/lib/BibSpace/files/templates/types/types.html.ep +++ b/lib/BibSpace/files/templates/types/types.html.ep @@ -67,7 +67,10 @@ -

    <%= $otype->our_type %>

    +

    + + <%= $otype->our_type %> +

    diff --git a/lib/BibStyle/LocalBibStyle.pm b/lib/BibStyle/LocalBibStyle.pm index f17eeef..4c55b55 100644 --- a/lib/BibStyle/LocalBibStyle.pm +++ b/lib/BibStyle/LocalBibStyle.pm @@ -5,7 +5,7 @@ package BibStyle::LocalBibStyle v0.0.4; =head1 NAME Text::BibTeX::BibStyle - Format Text::BibTeX::Entry items using .bst -This is a local copy of Text::BibTeX::BibStyle (v 0.0.3) from CPAN. +This is a local copy of Text::BibTeX::BibStyle (v 0.0.3) from CPAN. This version will be pushed to CPAN once tests are ready =cut @@ -1262,7 +1262,26 @@ under the same terms as Perl itself. # We can use builtin eval for these functions my $arg1 = $self->_pop(i => $token, 1); my $arg2 = $self->_pop(i => $token); - $self->_push(_check_type_warnings() ? 0 : eval "0+($arg2 $token $arg1)"); + + # Original code + # $self->_push(_check_type_warnings() ? 0 : eval "0+($arg2 $token $arg1)"); + # + # Replace eval with safe code due to warnings: + # Insecure dependency in open while running with -t switch + my $result = 0; + if ($token =~ '[*<>+-]') { + $result = 1 if $arg2 > $arg1 and $token eq '>'; + $result = 1 if $arg2 < $arg1 and $token eq '<'; + + $result = 0 + $arg2 + $arg1 if $token eq '+'; + $result = 0 + $arg2 - $arg1 if $token eq '-'; + $result = 0 + $arg2 * $arg1 if $token eq '*'; + + $self->_push(_check_type_warnings() ? 0 : $result); + } + else { + $self->_warning("UNSUPPORTED TOKEN $token"); + } } sub _function_assign { diff --git a/t/000_prepare.t b/t/000_prepare.t index 5ca0d7d..6e20c79 100644 --- a/t/000_prepare.t +++ b/t/000_prepare.t @@ -8,8 +8,8 @@ use Try::Tiny; use BibSpace; use BibSpace::Model::Backup; -use BibSpace::Functions::BackupFunctions qw(restore_storable_backup); -use BibSpace::Functions::FDB; # TODO: purge DB etc. +use BibSpace::Functions::BackupFunctions qw(restore_json_backup); +use BibSpace::Functions::FDB; `rm log/*.log`; `rm bibspace.dat`; @@ -36,7 +36,7 @@ ok(db_connect($db_host, $db_user, $db_database, $db_pass), "Can connect to database"); $dbh = $self->app->db; -my $fixture_file = $self->app->home->rel_file('fixture/bibspace_fixture.dat'); +my $fixture_file = $self->app->home->rel_file('fixture/bibspace_fixture.json'); my $fixture_name = '' . $fixture_file->basename; my $fixture_dir = '' . $fixture_file->dirname; @@ -55,10 +55,7 @@ SKIP: { note "Find backup file"; my $fixture = Backup->new(dir => $fixture_dir, filename => $fixture_name); - note "restore_storable_backup - read data into all layers"; - - # this restores data to all layers! - restore_storable_backup($fixture, $self->app); + restore_json_backup($fixture, $self->app); } diff --git a/t/100-unit/Author.t b/t/100-unit/Author.t index 49165cd..693c045 100644 --- a/t/100-unit/Author.t +++ b/t/100-unit/Author.t @@ -20,55 +20,82 @@ my @all_authors = $repo->authors_all; my $limit_test_objects = 30; -my $other_author = $self->app->entityFactory->new_Author(uid => "Henry"); +sub aDump { + JSON->new->convert_blessed->utf8->pretty->encode(shift); +} + +ok($self->app->repo->lr->facade, "Repository layer has facade set"); + +my $au = $self->app->entityFactory->new_Author(uid => "Henry"); +is($au->id, undef, "Unsaved Authors have no ID. Dump:" . aDump $au); +is($au->get_master_id, undef, + "Unsaved Authors have no master_ID. Dump:" . aDump $au); +$repo->authors_save($au); +isnt($au->id, undef, "Saved Authors have ID. Dump:" . aDump $au); +isnt($au->get_master_id, undef, + "Saved Authors have master_ID. Dump:" . aDump $au); subtest 'Author constructor' => sub { - is($other_author->master, $other_author->uid, "master name same as uid"); - is($other_author->master_id, $other_author->id, "master_id name same as id"); - is($other_author->masterObj, undef, "masterObj empty"); + ok($au->id >= 1, "id should be >= 1. Dump: " . aDump $au); + ok($au->get_master_id >= 1, "master_id should be >= 1. Dump: " . aDump $au); + is($au->name, $au->uid, "name should be same as uid. Dump: " . aDump $au); + is($au->get_master_id, $au->id, "master_id name same as id"); + isnt($au->get_master, undef, "masterObj should never be empty"); + is($au->get_master, $au, "get_master should return self"); }; subtest 'Alone author functions' => sub { - is($other_author->can_be_deleted, 1, "can_be_deleted"); + is($au->can_be_deleted, 1, "can_be_deleted"); }; note "============ Testing " . scalar(@all_authors) . " Authors ============"; foreach my $author (@all_authors) { last if $limit_test_objects < 0; + next if $author->id == $au->id; note "============ Testing Author ID " . $author->id . "."; - ok($author, "author defined"); - ok($author->get_master, "author->get_master defined"); + ok($author, "author should be defined"); + ok($author->get_master, "author->get_master should be defined"); + is($author->get_master, $author, "get_master should return self"); - $author->set_master($other_author); + note "============ Setting master " + . aDump($au) + . " to author " + . aDump($author) + . "============"; + + $author->set_master($au); isnt($author->is_master, 1, "isnt master"); - is($author->is_minion, 1, "is minion"); - is($author->is_minion_of($other_author), 1, "is_minion_of"); + is($author->get_master, $au, + "get_master should return master. Dump: " . aDump $author); + is($author->is_minion, 1, "is minion"); + is($author->is_minion_of($au), 1, "is_minion_of"); isnt($author->is_minion_of($author), 1, "isnt_minion_of"); - is($author->get_master, $other_author, "get_master"); - - is($author->update_master_name("John"), 1, "update master name"); - isnt($author->master, "John"); - is($author->uid, "John", "uid is John"); - ok($author->remove_master, "remove master"); - is($author->update_master_name("John"), 1); - is($author->master, "John", "master is John"); - is($author->uid, "John", "uid is John"); - ok($other_author->add_minion($author), "add minion"); + is($author->update_name("John"), + 1, "update master name should succeed. Dump: " . aDump $author); + isnt($author->master->name, + "New master name should be John. Dump: " . aDump $author); + is($author->uid, "John", "New uid should be John. Dump: " . aDump $author); + ok($author->remove_master, "remove master should succeed"); + is($author->update_name("John"), 1, "update_name should return 1"); + is($author->master->name, "John", "New master name should be John"); + is($author->uid, "John", "New uid should be John"); + + ok($au->add_minion($author), "add minion"); isnt($author->is_master, 1, "isnt master"); - is($author->is_minion, 1, "is minion"); - is($author->is_minion_of($other_author), 1, "is_minion_of"); + is($author->is_minion, 1, "is minion"); + is($author->is_minion_of($au), 1, "is_minion_of"); isnt($author->is_minion_of($author), 1, "isnt_minion_of"); - is($author->get_master, $other_author, "get_master"); + is($author->get_master, $au, "get_master"); - is($other_author->can_merge_authors($author), 1, "can_merge_authors"); - is($author->can_merge_authors($other_author), 1, "can_merge_authors"); - isnt($author->can_merge_authors($author), 1, "can_merge_authors"); - isnt($other_author->can_merge_authors($other_author), 1, "can_merge_authors"); + is($au->can_merge_authors($author), 1, "can_merge_authors"); + is($author->can_merge_authors($au), 1, "can_merge_authors"); + isnt($author->can_merge_authors($author), 1, "can_merge_authors"); + isnt($au->can_merge_authors($au), 1, "can_merge_authors"); if ($author->is_visible) { $author->toggle_visibility; @@ -93,17 +120,13 @@ foreach my $author (@all_authors) { note "Checking entry " . $entry->id; - my $au = Authorship->new( - author => $author, - entry => $entry, + my $au = $self->app->entityFactory->new_Authorship( author_id => $author->id, entry_id => $entry->id ); - ok($au->validate, 'validate'); - ok($au->equals($au), 'equals'); - ok($au->equals_id($au), 'equals_id'); - ok($au->equals_obj($au), 'equals_obj'); + ok($au->equals($au), 'equals'); + ok($au->equals_id($au), 'equals_id'); if (!$author->has_authorship($au)) { ok($author->add_authorship($au), 'add_authorship'); @@ -113,11 +136,18 @@ foreach my $author (@all_authors) { } ok($author->has_entry($entry), 'has_entry'); ok($author->has_authorship($au), 'has_authorship'); - is($author->remove_authorship($au), 1, 'remove authorship'); - ok(!$author->has_authorship($au), 'hasnt authorship'); - ok(!$author->remove_authorship($au), 'cant remove authorship'); - ok(!$author->has_authorship($au), 'hasnt authorship'); - ok(!$author->has_entry($entry), "hasn't entry"); + is($author->remove_authorship($au), + 1, 'Authorship should be removed with result 1'); + ok(!$author->has_authorship($au), + 'Author should no longer have authorship'); + is($author->remove_authorship($au), + undef, 'Removal of non-existing authorship removal should return undef'); + ok(!$author->has_authorship($au), 'hasnt authorship'); + ok(!$author->has_entry($entry), + "Author should not have entry " + . aDump($author) + . " Entry: " + . aDump($entry)); } diff --git a/t/100-unit/Authorship.t b/t/100-unit/Authorship.t new file mode 100644 index 0000000..0de311c --- /dev/null +++ b/t/100-unit/Authorship.t @@ -0,0 +1,58 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use Test::Exception; + +my $t_anyone = Test::Mojo->new('BibSpace'); +my $self = $t_anyone->app; + +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +my $repo = $self->app->repo; +my @all_authorships = $repo->authorships_all; + +my $limit_num_tests = 200; + +note "============ Testing " + . scalar(@all_authorships) + . " entries ============"; + +foreach my $authorship (@all_authorships) { + last if $limit_num_tests < 0; + + note "============ Testing Labeling ID " . $authorship->id . "."; + + ok($authorship->id, "id"); + + lives_ok(sub { $authorship->equals($authorship) }, + 'equals expecting to live'); + lives_ok(sub { $authorship->equals_id($authorship) }, + 'equals_id expecting to live'); + dies_ok(sub { $authorship->equals(undef) }, 'equals undef expecting to die'); + + $limit_num_tests--; +} + +subtest "Serializing to JSON should not remove fields from super-class" => sub { + use JSON -convert_blessed_universally; + my $l = $all_authorships[0]; + + my $pre_1 = $l->entry_id; + my $pre_2 = $l->author_id; + + my $json = JSON->new->convert_blessed->utf8->pretty->encode($l); + chomp $json; + cmp_ok($json, 'ne', "{}", "JSON representation should not be empty"); + + my $post_1 = $l->entry_id; + my $post_2 = $l->author_id; + + is($post_1, $pre_1, + "Data field entry_id should be identical before and after serialization"); + is($post_2, $pre_2, + "Data field author_id should be identical before and after serialization"); +}; + +ok(1); +done_testing(); diff --git a/t/100-unit/BibSpaceDTO-Date-Formatting.t b/t/100-unit/BibSpaceDTO-Date-Formatting.t index 43e27bb..1b16d3b 100644 --- a/t/100-unit/BibSpaceDTO-Date-Formatting.t +++ b/t/100-unit/BibSpaceDTO-Date-Formatting.t @@ -66,11 +66,9 @@ subtest 'DTO restore Entry from JSON and non-standard date-time format' => sub { "Json string should be blessable into BibSpaceDTO class"; # Creates DTO. Requires repo only for uid provider and preferences - $decodedDTO = $bibspaceDTOObject->toLayeredRepo( - $singleEntryJSON_non_standard_date_time, - $self->repo->lr->uidProvider, - $self->repo->lr->preferences - ); + $decodedDTO + = $bibspaceDTOObject->toLayeredRepo($singleEntryJSON_non_standard_date_time, + $self->repo); isa_ok($decodedDTO, 'BibSpaceDTO', "Decoded class should be BibSpaceDTO, but is " . ref $decodedDTO); is( diff --git a/t/100-unit/BibSpaceDTO.t b/t/100-unit/BibSpaceDTO.t index 230dcc3..f180086 100644 --- a/t/100-unit/BibSpaceDTO.t +++ b/t/100-unit/BibSpaceDTO.t @@ -2,6 +2,8 @@ use Mojo::Base -strict; use Test::More; use Test::Exception; use Test::Mojo; +use Data::Dumper; +$Data::Dumper::Maxdepth = 2; my $t_logged_in = Test::Mojo->new('BibSpace'); $t_logged_in->post_ok( @@ -14,17 +16,38 @@ TestManager->apply_fixture($self->app); use JSON -convert_blessed_universally; -my $bibspaceDTOObject = BibSpaceDTO->fromLayeredRepo($self->repo); -my $jsonString = $bibspaceDTOObject->toJSON(); +subtest 'DTO serialization to JSON should not change DTO self-object' => sub { + my $referenceDTOfromRepo = BibSpaceDTO->fromLayeredRepo($self->repo); + my $labelings0 = $referenceDTOfromRepo->get('Labeling'); + my $l0 = $labelings0->[0]; + + isa_ok $l0, 'Labeling', "Object before serialization should be Labeling"; + isa_ok $l0, 'Labeling', + "Object before serialization should be Labeling (second check)"; + + my $referenceDTOasJSON = $referenceDTOfromRepo->toJSON(); + + my $labelings1 = $referenceDTOfromRepo->get('Labeling'); + my $l1 = $labelings1->[0]; + + isa_ok $l0, 'Labeling', + "Object fetched before serialization should be isa Labeling after serialization"; + isa_ok $l1, 'Labeling', + "Object fetched after serialization should be isa Labeling"; + +}; + +my $referenceDTOfromRepo = BibSpaceDTO->fromLayeredRepo($self->repo); +my $referenceDTOasJSON = $referenceDTOfromRepo->toJSON(); subtest 'DTO create' => sub { - ok($jsonString, "Shall produce non-empty string"); - lives_ok { JSON->new->decode($jsonString) } + ok($referenceDTOasJSON, "Shall produce non-empty string"); + lives_ok { JSON->new->decode($referenceDTOasJSON) } "Json string should be valid - decodable"; - ok(JSON->new->decode($jsonString), + ok(JSON->new->decode($referenceDTOasJSON), "Json string should return non-undef object after decode"); lives_ok { - bless(JSON->new->decode($jsonString), 'BibSpaceDTO') + bless(JSON->new->decode($referenceDTOasJSON), 'BibSpaceDTO') } "JSON string should be blessable back to BibSpaceDTO"; }; @@ -35,9 +58,7 @@ subtest 'Type Objects Should have arrays serialized properly' => sub { $type->bibtexTypes_add("Article"); is($type->get_first_bibtex_type, 'Article'); - use JSON -convert_blessed_universally; - my $json_obj = JSON->new->convert_blessed->utf8->pretty; - my $jStr = $json_obj->encode($type); + my $jStr = JSON->new->convert_blessed->utf8->pretty->encode($type); unlike( $jStr, @@ -49,88 +70,111 @@ subtest 'Type Objects Should have arrays serialized properly' => sub { qr/bibtexTypes" : \[\W*Article\W*\]/s, "JSON should contain 'Article' in the 'bibtexTypes' array" ); - -# unlike($jsonString, qr/bibtexTypes" : \[\]/, "JSON should not contain empty 'bibtexTypes' arrays"); }; -subtest 'DTO restore from JSON' => sub { +subtest 'DTO restore from JSON' => sub { use Try::Tiny; - my $decodedJson; try { - $decodedJson = JSON->new->decode($jsonString); + my $decodedReferenceJSONhash = JSON->new->decode($referenceDTOasJSON); } finally { # Just mute errors }; - my $decodedDTO; lives_ok { - $decodedDTO = bless(JSON->new->decode($jsonString), 'BibSpaceDTO') + my $decodedReferenceJSONhash = JSON->new->decode($referenceDTOasJSON); + bless($decodedReferenceJSONhash, 'BibSpaceDTO') } - "Json string should be blessable into BibSpaceDTO class"; + "Json string should be decodable into hash and hash should be blessable into BibSpaceDTO class"; - $decodedDTO = $bibspaceDTOObject->toLayeredRepo( - $jsonString, - $self->repo->lr->uidProvider, - $self->repo->lr->preferences - ); - isa_ok($decodedDTO, 'BibSpaceDTO', - "Decoded class should be BibSpaceDTO, but is " . ref $decodedDTO); + note + "Create rich BibSpaceDTO object from parsed JSON that has been blessed into BibSpaceDTO"; + + my $restoredDTO + = BibSpaceDTO->toLayeredRepo($referenceDTOasJSON, $self->repo); + + isa_ok($restoredDTO, 'BibSpaceDTO', + "Decoded class should be BibSpaceDTO, but is " . ref $restoredDTO); is( - ref $decodedDTO, - ref $bibspaceDTOObject, - "Obj type should be " . ref $bibspaceDTOObject + ref $restoredDTO, + ref $referenceDTOfromRepo, + "Obj type should be " . ref $referenceDTOfromRepo ); is( - ref $decodedDTO->data, - ref $bibspaceDTOObject->data, - "->data type should be " . ref $bibspaceDTOObject->data + ref $restoredDTO->data, + ref $referenceDTOfromRepo->data, + "restoredDTO->data type should be " . ref $referenceDTOfromRepo->data ); - isa_ok($decodedDTO->data, 'HASH', "Bad class, " . ref $decodedDTO->data); - - for my $entity (keys %{$decodedDTO->data}) { - note("Testing collection holding objects of type $entity"); - my $value = $decodedDTO->data->{$entity}; - my $expectedValue = $bibspaceDTOObject->data->{$entity}; + isa_ok($restoredDTO->data, 'HASH', "Bad class, " . ref $restoredDTO->data); + + for my $restoredDTOHashKey (keys %{$restoredDTO->data}) { + note("Testing collection holding objects of type $restoredDTOHashKey"); + my $value + = $restoredDTO->get($restoredDTOHashKey); #data->{$restoredDTOHashKey}; + my $expectedValue = $referenceDTOfromRepo->get($restoredDTOHashKey) + ; #data->{$restoredDTOHashKey}; + # print Dumper $expectedValue; + # $VAR1 = [ + # bless( { + # 'team_id' => 1, + # 'entry_id' => 588 + # }, 'ExceptionSerializableBase' ), + # bless( { + # 'team_id' => 6, + # 'entry_id' => 588 + # }, 'ExceptionSerializableBase' ), + # bless( { + # 'team_id' => 6, + # 'entry_id' => 1173 + # }, 'ExceptionSerializableBase' ) + # ]; is( ref $value, ref $expectedValue, - "->data->{$entity} should be " . ref $expectedValue + "restoredDTO->data->{$restoredDTOHashKey} should be " + . ref $expectedValue ); # FIXME: Assume that there is at least 1 object of each type stored - my $obj = $value->[0]; - my $exObj = $expectedValue->[0]; - is(ref $obj, ref $exObj, "->data->{$entity}->[0] should be " . ref $exObj); + my $gotObject = $value->[0]; + my $expectedObject = $expectedValue->[0]; + is( + ref $gotObject, + ref $expectedObject, + "restoredDTO->data->{$restoredDTOHashKey}->[0] should be " + . ref $expectedObject + ); } - my $oTest = $decodedDTO->data->{'Entry'}->[0]->creation_time; - my $oExpected = $bibspaceDTOObject->data->{'Entry'}->[0]->creation_time; + my $oTest = $restoredDTO->data->{'Entry'}->[0]->creation_time; + my $oExpected = $referenceDTOfromRepo->data->{'Entry'}->[0]->creation_time; is( ref $oTest, ref $oExpected, - "->data->{Entry}->[0]->creation_time should be " . ref $oExpected + "restoredDTO->data->{Entry}->[0]->creation_time should be " + . ref $oExpected ); TODO: { local $TODO = "Do not bless but use constructor instead!"; - my $oTest = $decodedDTO->data->{'Entry'}->[0]->attachments; - my $oExpected = $bibspaceDTOObject->data->{'Entry'}->[0]->attachments; + my $oTest = $restoredDTO->data->{'Entry'}->[0]->attachments; + my $oExpected = $referenceDTOfromRepo->data->{'Entry'}->[0]->attachments; is( ref $oTest, ref $oExpected, - "->data->{Entry}->[0]->attachments should be " . ref $oExpected + "restoredDTO->data->{Entry}->[0]->attachments should be " + . ref $oExpected ); } TODO: { local $TODO = "Do not bless but use constructor instead!"; - my $oTest = $decodedDTO->data->{'Entry'}->[0]->title; - my $oExpected = $bibspaceDTOObject->data->{'Entry'}->[0]->title; + my $oTest = $restoredDTO->data->{'Entry'}->[0]->title; + my $oExpected = $referenceDTOfromRepo->data->{'Entry'}->[0]->title; is( ref $oTest, ref $oExpected, - "->data->{Entry}->[0]->title should be " . ref $oExpected + "restoredDTO->data->{Entry}->[0]->title should be " . ref $oExpected ); } }; @@ -174,22 +218,18 @@ subtest 'DTO restore Entry from JSON and check blessing to DateTime' => sub { }; my $decodedDTO; lives_ok { - $decodedDTO = bless(JSON->new->decode($jsonString), 'BibSpaceDTO') + $decodedDTO = bless(JSON->new->decode($referenceDTOasJSON), 'BibSpaceDTO') } "Json string should be blessable into BibSpaceDTO class"; # Creates DTO. Requires repo only for uid provider and preferences - $decodedDTO = $bibspaceDTOObject->toLayeredRepo( - $singleEntryJSON, - $self->repo->lr->uidProvider, - $self->repo->lr->preferences - ); + $decodedDTO = BibSpaceDTO->toLayeredRepo($singleEntryJSON, $self->repo); isa_ok($decodedDTO, 'BibSpaceDTO', "Decoded class should be BibSpaceDTO, but is " . ref $decodedDTO); is( ref $decodedDTO, - ref $bibspaceDTOObject, - "Obj type should be of type " . ref $bibspaceDTOObject + ref $referenceDTOfromRepo, + "Obj type should be of type " . ref $referenceDTOfromRepo ); is(ref $decodedDTO->data, 'HASH', "->data type should be HASH"); @@ -199,9 +239,9 @@ subtest 'DTO restore Entry from JSON and check blessing to DateTime' => sub { my $value = $decodedDTO->data->{'Entry'}; is(ref $value, 'ARRAY', "->data->{'Entry'} should be ARRAY"); - my $obj = $value->[0]; - is(ref $obj, 'Entry', "->data->{'Entry'}->[0] should be Entry"); - is(ref $obj->creation_time, + my $gotObject = $value->[0]; + is(ref $gotObject, 'Entry', "->data->{'Entry'}->[0] should be Entry"); + is(ref $gotObject->creation_time, 'DateTime', "->data->{'Entry'}->[0]->creation_time should be DateTime"); }; diff --git a/t/100-unit/Entry-OurType-Mapping.t b/t/100-unit/Entry-OurType-Mapping.t index ae806ef..9b72b2a 100644 --- a/t/100-unit/Entry-OurType-Mapping.t +++ b/t/100-unit/Entry-OurType-Mapping.t @@ -114,11 +114,7 @@ subtest 'Restore Entries from JSON' => sub { my $decodedDTO; # Creates DTO. Requires repo only for uid provider and preferences - $decodedDTO = $bibspaceDTOObject->toLayeredRepo( - $twoEntriesJSON, - $self->repo->lr->uidProvider, - $self->repo->lr->preferences - ); + $decodedDTO = $bibspaceDTOObject->toLayeredRepo($twoEntriesJSON, $self->repo); isa_ok($decodedDTO, 'BibSpaceDTO', "Decoded class should be BibSpaceDTO, but is " . ref $decodedDTO); @@ -132,5 +128,4 @@ subtest 'Repeat subtest: Compare entries with types: inproceedings and incollection before restoring backup' => \&compare_types; -ok(1); done_testing(); diff --git a/t/100-unit/Entry.t b/t/100-unit/Entry.t index 90f6625..352a90f 100644 --- a/t/100-unit/Entry.t +++ b/t/100-unit/Entry.t @@ -17,7 +17,7 @@ my $repo = $self->app->repo; my @all_entries = $repo->entries_all; -my $limit_test_entries = 20; +my $limit_test_entries = 10; note "============ Testing " . scalar(@all_entries) . " Entries ============"; @@ -25,6 +25,15 @@ foreach my $entry (@all_entries) { last if $limit_test_entries < 0; note ">> Testing Entry ID " . $entry->id . "."; + ok($entry->equals($entry), "Entry is equal to itself"); + dies_ok(sub { $entry->equals(undef) }, 'equals undef expecting to die'); + + ok($entry->equals_bibtex($entry), "Entry is equal_bibtex to itself"); + dies_ok( + sub { $entry->equals_bibtex(undef) }, + 'equal_bibtex undef expecting to die' + ); + $entry->make_paper; ok($entry->is_paper); $entry->make_talk; @@ -54,8 +63,7 @@ foreach my $entry (@all_entries) { ok($entry->fix_month, "month fixed"); is($entry->month, 4, "month fixed correctly"); - if ($entry->has_bibtex_field('author') or $entry->has_bibtex_field('editor')) - { + if ($entry->has_bibtex_field('author')) { my @author_names = $entry->author_names_from_bibtex; ok(scalar @author_names > 0, "Entry has authors in bibtex"); } @@ -65,6 +73,21 @@ foreach my $entry (@all_entries) { is(scalar @author_names, 1, "Entry has 1 author in bibtex"); } + note "Testing case where there are no authors but there are editors"; + $entry->remove_bibtex_fields(['author']); + if ($entry->has_bibtex_field('editor')) { + my @author_names = $entry->author_names_from_bibtex; + ok(scalar @author_names > 0, "Entry has editors in bibtex"); + } + else { + $entry->add_bibtex_field("editor", "James Bond"); + my @author_names = $entry->author_names_from_bibtex; + is(scalar @author_names, 1, "Entry has 1 editor in bibtex"); + } + + # readd removed field + $entry->add_bibtex_field("author", "James Bond"); + if ($entry->has_bibtex_field('tags')) { is($entry->remove_bibtex_fields(['tags']), 1, "remove tags bibtex field"); my @no_tag_names = $entry->tag_names_from_bibtex; @@ -89,5 +112,4 @@ foreach my $entry (@all_entries) { my $broken_entry = $self->app->entityFactory->new_Entry(bib => ' '); $broken_entry->regenerate_html(1, $self->app->bst, $self->app->bibtexConverter); -ok(1); done_testing(); diff --git a/t/100-unit/Exception.t b/t/100-unit/Exception.t index 167315c..3328721 100644 --- a/t/100-unit/Exception.t +++ b/t/100-unit/Exception.t @@ -9,6 +9,10 @@ my $self = $t_anyone->app; use BibSpace::TestManager; TestManager->apply_fixture($self->app); +sub aDump { + JSON->new->convert_blessed->utf8->pretty->encode(shift); +} + my $repo = $self->app->repo; my @all_exceptions = $repo->exceptions_all; @@ -16,25 +20,43 @@ my $limit_num_tests = 200; note "============ Testing " . scalar(@all_exceptions) - . " entries ============"; + . " exceptions ============"; foreach my $exception (@all_exceptions) { last if $limit_num_tests < 0; - note "============ Testing Exception ID " . $exception->id . "."; + note "============ Testing Exception ID " . aDump($exception) . "."; - ok($exception->id, "id"); + ok($exception->id, "Exception id should be defined"); - # lives_ok( sub { $exception->validate }, 'validate expecting to live' ); - lives_ok(sub { $exception->equals($exception) }, 'equals expecting to live'); - lives_ok(sub { $exception->equals_obj($exception) }, - 'equals_obj expecting to live'); + lives_ok(sub { $exception->equals($exception) }, + 'equals sub should not throw'); lives_ok(sub { $exception->equals_id($exception) }, - 'equals_id expecting to live'); + 'equals_id sub should not thorw'); dies_ok(sub { $exception->equals(undef) }, 'equals undef expecting to die'); $limit_num_tests--; } +subtest "Serializing to JSON should not remove fields from super-class" => sub { + use JSON -convert_blessed_universally; + my $l = $all_exceptions[0]; + + my $pre_1 = $l->entry_id; + my $pre_2 = $l->team_id; + + my $json = JSON->new->convert_blessed->utf8->pretty->encode($l); + chomp $json; + cmp_ok($json, 'ne', "{}", "JSON representation should not be empty"); + + my $post_1 = $l->entry_id; + my $post_2 = $l->team_id; + + is($post_1, $pre_1, + "Data field entry_id should be identical before and after serialization"); + is($post_2, $pre_2, + "Data field team_id should be identical before and after serialization"); +}; + ok(1); done_testing(); diff --git a/t/100-unit/Labeling.t b/t/100-unit/Labeling.t index 72dde73..37aceaf 100644 --- a/t/100-unit/Labeling.t +++ b/t/100-unit/Labeling.t @@ -12,7 +12,7 @@ TestManager->apply_fixture($self->app); my $repo = $self->app->repo; my @all_labelings = $repo->labelings_all; -my $limit_num_tests = 200; +my $limit_num_tests = 50; note "============ Testing " . scalar(@all_labelings) . " entries ============"; @@ -23,10 +23,7 @@ foreach my $labeling (@all_labelings) { ok($labeling->id, "id"); - lives_ok(sub { $labeling->validate }, 'validate expecting to live'); lives_ok(sub { $labeling->equals($labeling) }, 'equals expecting to live'); - lives_ok(sub { $labeling->equals_obj($labeling) }, - 'equals_obj expecting to live'); lives_ok(sub { $labeling->equals_id($labeling) }, 'equals_id expecting to live'); dies_ok(sub { $labeling->equals(undef) }, 'equals undef expecting to die'); @@ -34,5 +31,25 @@ foreach my $labeling (@all_labelings) { $limit_num_tests--; } +subtest "Serializing to JSON should not remove fields from super-class" => sub { + use JSON -convert_blessed_universally; + my $l = $all_labelings[0]; + + my $pre_1 = $l->entry_id; + my $pre_2 = $l->tag_id; + + my $json = JSON->new->convert_blessed->utf8->pretty->encode($l); + chomp $json; + cmp_ok($json, 'ne', "{}", "JSON representation should not be empty"); + + my $post_1 = $l->entry_id; + my $post_2 = $l->tag_id; + + is($post_1, $pre_1, + "Data field entry_id should be identical before and after serialization"); + is($post_2, $pre_2, + "Data field tag_id should be identical before and after serialization"); +}; + ok(1); done_testing(); diff --git a/t/100-unit/Membership.t b/t/100-unit/Membership.t new file mode 100644 index 0000000..e5e08d5 --- /dev/null +++ b/t/100-unit/Membership.t @@ -0,0 +1,66 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use Test::Exception; + +my $t_anyone = Test::Mojo->new('BibSpace'); +my $self = $t_anyone->app; + +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +my $repo = $self->app->repo; +my @all_memberships = $repo->memberships_all; + +my $limit_num_tests = 200; + +note "============ Testing " + . scalar(@all_memberships) + . " entries ============"; + +foreach my $membership (@all_memberships) { + last if $limit_num_tests < 0; + + note "============ Testing Labeling ID " . $membership->id . "."; + + ok($membership->id, "id"); + + lives_ok(sub { $membership->equals($membership) }, + 'equals expecting to live'); + lives_ok(sub { $membership->equals_id($membership) }, + 'equals_id expecting to live'); + dies_ok(sub { $membership->equals(undef) }, 'equals undef expecting to die'); + + $limit_num_tests--; +} + +subtest "Serializing to JSON should not remove fields from super-class" => sub { + use JSON -convert_blessed_universally; + my $l = $all_memberships[0]; + + my $pre_1 = $l->team_id; + my $pre_2 = $l->author_id; + my $pre_3 = $l->start; + my $pre_4 = $l->stop; + + my $json = JSON->new->convert_blessed->utf8->pretty->encode($l); + chomp $json; + cmp_ok($json, 'ne', "{}", "JSON representation should not be empty"); + + my $post_1 = $l->team_id; + my $post_2 = $l->author_id; + my $post_3 = $l->start; + my $post_4 = $l->stop; + + is($post_1, $pre_1, + "Data field team_id should be identical before and after serialization"); + is($post_2, $pre_2, + "Data field author_id should be identical before and after serialization"); + is($post_3, $pre_3, + "Data field start should be identical before and after serialization"); + is($post_4, $pre_4, + "Data field stop should be identical before and after serialization"); +}; + +ok(1); +done_testing(); diff --git a/t/100-unit/RepositoryFacade.t b/t/100-unit/RepositoryFacade.t index 338e35e..fc2f8a1 100644 --- a/t/100-unit/RepositoryFacade.t +++ b/t/100-unit/RepositoryFacade.t @@ -6,186 +6,242 @@ use Test::Exception; use Data::Dumper; use Array::Utils qw(:all); use feature qw( say ); -use BibSpace::Repository::RepositoryFacade; +use BibSpace::Repository::FlatRepositoryFacade; my $t_anyone = Test::Mojo->new('BibSpace'); my $self = $t_anyone->app; -use BibSpace::TestManager; -TestManager->apply_fixture($self->app); +# For this test, we need empty database +my @layers = $self->repo->lr->get_all_layers; +foreach (@layers) { $_->reset_data } -my $mock_obj_authors = $self->app->entityFactory->new_Author(uid => 'test'); -my $mock_obj_entries = $self->app->entityFactory->new_Entry(bib => 'test'); -my $mock_obj_tags = $self->app->entityFactory->new_Tag(name => 'test'); -my $mock_obj_tagTypes = $self->app->entityFactory->new_TagType(name => 'test'); -my $mock_obj_teams = $self->app->entityFactory->new_Team(name => 'test'); +sub aDump { + use JSON -convert_blessed_universally; + JSON->new->convert_blessed->utf8->pretty->encode(shift); +} + +my $mock_obj_authors + = $self->app->entityFactory->new_Author(id => -1, uid => 'test'); +my $mock_obj_entries + = $self->app->entityFactory->new_Entry(id => -1, bib => 'test'); +my $mock_obj_tags + = $self->app->entityFactory->new_Tag(id => -1, name => 'test'); +my $mock_obj_tagTypes + = $self->app->entityFactory->new_TagType(id => -1, name => 'test', type => 1); +my $mock_obj_teams + = $self->app->entityFactory->new_Team(id => -1, name => 'test'); my $mock_obj_types = $self->app->entityFactory->new_Type(our_type => 'test'); +$mock_obj_types->bibtexTypes_add("Article"); + my $mock_obj_users = $self->app->entityFactory->new_User( + id => -1, login => 'test', email => 'test', pass => 'test', pass2 => 'test' ); -my $mock_obj_authorships = Authorship->new( +# Check if repo is empty and add mock objects to DB +# Entities +my @objs; + +is($t_anyone->app->repo->authors_empty, 1, "authors_empty should be 1"); +ok($mock_obj_authors, "mock_obj_authors: " . aDump($mock_obj_authors)); +$t_anyone->app->repo->authors_save($mock_obj_authors); +@objs = $t_anyone->app->repo->authors_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->id > 0, "Added object should have ID > 0" . aDump(@objs)); + +is($t_anyone->app->repo->entries_empty, 1, "entries_empty should be 1"); +ok($mock_obj_entries, "mock_obj_entries: " . aDump($mock_obj_entries)); +$t_anyone->app->repo->entries_save($mock_obj_entries); +@objs = $t_anyone->app->repo->entries_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->id > 0, "Added object should have ID > 0" . aDump(@objs)); + +is($t_anyone->app->repo->tagTypes_empty, 1, "tagTypes_empty should be 1"); +ok($mock_obj_tagTypes, "mock_obj_tagTypes: " . aDump($mock_obj_tagTypes)); +$t_anyone->app->repo->tagTypes_save($mock_obj_tagTypes); +@objs = $t_anyone->app->repo->tagTypes_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->id > 0, "Added object should have ID > 0" . aDump(@objs)); + +is($t_anyone->app->repo->tags_empty, 1, "tags_empty should be 1"); +ok($mock_obj_tags, "mock_obj_tags: " . aDump($mock_obj_tags)); +$t_anyone->app->repo->tags_save($mock_obj_tags); +@objs = $t_anyone->app->repo->tags_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->id > 0, "Added object should have ID > 0" . aDump(@objs)); + +is($t_anyone->app->repo->teams_empty, 1, "teams_empty should be 1"); +ok($mock_obj_teams, "mock_obj_teams: " . aDump($mock_obj_teams)); +$t_anyone->app->repo->teams_save($mock_obj_teams); +@objs = $t_anyone->app->repo->teams_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->id > 0, "Added object should have ID > 0" . aDump(@objs)); + +is($t_anyone->app->repo->types_empty, 1, "types_empty should be 1"); +ok($mock_obj_types, "mock_obj_types: " . aDump($mock_obj_types)); +$t_anyone->app->repo->types_save($mock_obj_types); +@objs = $t_anyone->app->repo->types_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->our_type, + "Added object should have ID (here: our_type) defined" . aDump(@objs)); + +is($t_anyone->app->repo->users_empty, 1, "users_empty should be 1"); +ok($mock_obj_users, "mock_obj_users: " . aDump($mock_obj_users)); +$t_anyone->app->repo->users_save($mock_obj_users); +@objs = $t_anyone->app->repo->users_all; +ok($objs[0], "Added object should exist" . aDump(@objs)); +ok($objs[0]->id > 0, "Added object should have ID > 0" . aDump(@objs)); + +# Relations +my $mock_obj_authorships = $self->app->entityFactory->new_Authorship( entry_id => $mock_obj_entries->id, - entry => $mock_obj_entries, author_id => $mock_obj_authors->id, - author => $mock_obj_authors ); -my $mock_obj_exceptions = Exception->new( +my $mock_obj_exceptions = $self->app->entityFactory->new_Exception( entry_id => $mock_obj_entries->id, - entry => $mock_obj_entries, team_id => $mock_obj_teams->id, - team => $mock_obj_teams ); -my $mock_obj_labelings = Labeling->new( +my $mock_obj_labelings = $self->app->entityFactory->new_Labeling( entry_id => $mock_obj_entries->id, - entry => $mock_obj_entries, tag_id => $mock_obj_tags->id, - tag => $mock_obj_tags ); -my $mock_obj_memberships = Membership->new( +my $mock_obj_memberships = $self->app->entityFactory->new_Membership( team_id => $mock_obj_teams->id, - team => $mock_obj_teams, author_id => $mock_obj_authors->id, - author => $mock_obj_authors ); - -ok($mock_obj_authors, "mock_obj_authors: $mock_obj_authors "); -ok($mock_obj_authorships, "mock_obj_authorships: $mock_obj_authorships"); -ok($mock_obj_entries, "mock_obj_entries: $mock_obj_entries "); -ok($mock_obj_exceptions, "mock_obj_exceptions: $mock_obj_exceptions "); -ok($mock_obj_labelings, "mock_obj_labelings: $mock_obj_labelings "); -ok($mock_obj_memberships, "mock_obj_memberships: $mock_obj_memberships"); -ok($mock_obj_tags, "mock_obj_tags: $mock_obj_tags "); -ok($mock_obj_tagTypes, "mock_obj_tagTypes: $mock_obj_tagTypes "); -ok($mock_obj_teams, "mock_obj_teams: $mock_obj_teams "); -ok($mock_obj_types, "mock_obj_types: $mock_obj_types "); -ok($mock_obj_users, "mock_obj_users: $mock_obj_users "); - -my @actions0_defined = qw( all count ); -my @actions0_undef = qw( empty ); - -my @actions1 = qw( filter find ); - -my @actions2 = qw( save update exists ); # oder important because of exists! -my @actions2_delete = qw( delete ); - -my @objects = qw(authors teams entries tags tagTypes types users); -my @relations = qw(authorships exceptions labelings memberships ); +is($t_anyone->app->repo->authorships_empty, 1, "authorships_empty should be 1"); +ok($mock_obj_authorships, + "mock_obj_authorships: " . aDump($mock_obj_authorships)); +ok($mock_obj_authorships->author_id > 0, + "mock_obj_authorships: " . aDump($mock_obj_authorships)); +ok($mock_obj_authorships->entry_id > 0, + "mock_obj_authorships: " . aDump($mock_obj_authorships)); +$t_anyone->app->repo->authorships_save($mock_obj_authorships); + +is($t_anyone->app->repo->exceptions_empty, 1, "exceptions_empty should be 1"); +ok($mock_obj_exceptions, + "mock_obj_exceptions: " . aDump($mock_obj_exceptions)); +ok($mock_obj_exceptions->entry_id > 0, + "mock_obj_exceptions: " . aDump($mock_obj_exceptions)); +ok($mock_obj_exceptions->team_id > 0, + "mock_obj_exceptions: " . aDump($mock_obj_exceptions)); +$t_anyone->app->repo->exceptions_save($mock_obj_exceptions); + +is($t_anyone->app->repo->labelings_empty, 1, "labelings_empty should be 1"); +ok($mock_obj_labelings, "mock_obj_labelings: " . aDump($mock_obj_labelings)); +ok($mock_obj_labelings->entry_id > 0, + "mock_obj_labelings: " . aDump($mock_obj_labelings)); +ok($mock_obj_labelings->tag_id > 0, + "mock_obj_labelings: " . aDump($mock_obj_labelings)); +$t_anyone->app->repo->labelings_save($mock_obj_labelings); + +is($t_anyone->app->repo->memberships_empty, 1, "memberships_empty should be 1"); +ok($mock_obj_memberships, + "mock_obj_memberships: " . aDump($mock_obj_memberships)); +ok($mock_obj_memberships->author_id > 0, + "mock_obj_memberships: " . aDump($mock_obj_memberships)); +ok($mock_obj_memberships->team_id > 0, + "mock_obj_memberships: " . aDump($mock_obj_memberships)); +$t_anyone->app->repo->memberships_save($mock_obj_memberships); + +my $res; +my $element; +my $prefix; + +my @objects = qw(authors teams entries tags tagTypes types users); foreach my $obj (@objects) { - - foreach my $action (@actions0_defined) { - note "================= Testing action $action ================="; + foreach my $action ('all', 'count') { + note "=== $obj action $action ==="; my $code = '$t_anyone->app->repo->' . "$obj" . "_" . "$action;"; my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); - ok(!$@, "eval error empty"); + ok(!$@, "eval error empty. code: $code warn $@"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); + ok($res >= 1, $obj . "_$action - output: $res should be >= 1"); } - - foreach my $action (@actions0_undef) { - note "================= Testing action $action ================="; + foreach my $action ('empty') { + note "=== $obj action $action ==="; my $code = '$t_anyone->app->repo->' . "$obj" . "_" . "$action;"; my $res = eval "$code"; - is($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); - ok(!$@, "eval error empty"); + ok(!$@, "eval error empty. code: $code warn $@"); + is($res, undef, $obj . "_$action - output should be undef"); } - - foreach my $action (@actions1) { - note "================= Testing action $action ================="; + foreach my $action ('filter', 'find') { + note "=== $obj action $action ==="; my $code = '$t_anyone->app->repo->' . "$obj" . "_" - . "$action(sub {defined " . '$_' . "} );"; + . "$action(sub {defined \$_ });"; my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); + ok(!$@, "eval error empty. code: $code warn $@"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); } - - foreach my $action (@actions2) { - note "================= Testing action $action ================="; - - my $element = eval '$mock_obj_' . "$obj"; - say "element->id: " . $element->id; - ok($element, "element '$element' is defined. Error captured: '$@'."); - ok(!$@, "eval error empty"); - - my $code = 'my $element = ' . '$mock_obj_' . "$obj" . '; '; - $code - .= '$t_anyone->app->repo->' . "$obj" . "_" - . "$action( " - . '$element' . " );"; - + foreach my $action ('save', 'update') { + note "=== $obj action $action ==="; + my $code + = 'my $element = $mock_obj_' + . $obj + . '; $t_anyone->app->repo->' . "$obj" . "_" + . "$action(\$element);"; my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); - ok(!$@, "eval error empty"); + ok(!$@, "eval error empty. code: $code warn $@"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); } + # Delete will be tested at the end of this file } +my @relations = qw(authorships exceptions labelings memberships ); foreach my $obj (@relations) { - - foreach my $action (@actions0_defined) { - note "================= Testing action $action ================="; + foreach my $action ('all', 'count') { + note "=== $obj action $action ==="; my $code = '$t_anyone->app->repo->' . "$obj" . "_" . "$action;"; my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); - ok(!$@, "eval error empty"); + ok(!$@, "eval error empty. code: $code warn $@"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); + ok($res >= 1, $obj . "_$action - output: $res should be >= 1"); } - - foreach my $action (@actions0_undef) { - note "================= Testing action $action ================="; + foreach my $action ('empty') { + note "=== $obj action $action ==="; my $code = '$t_anyone->app->repo->' . "$obj" . "_" . "$action;"; my $res = eval "$code"; - is($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); - ok(!$@, "eval error empty"); + ok(!$@, "eval error empty. code: $code warn $@"); + is($res, undef, $obj . "_$action - output: $res should be undef"); } - - foreach my $action (@actions1) { - note "================= Testing action $action ================="; + foreach my $action ('filter', 'find') { + note "=== $obj action $action ==="; my $code = '$t_anyone->app->repo->' . "$obj" . "_" - . "$action(sub {defined " . '$_' . "} );"; + . "$action(sub {defined \$_ });"; my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); ok(!$@, "eval error empty"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); } - - foreach my $action (@actions2) { - note "================= Testing action $action ================="; - - my $element = eval '$mock_obj_' . "$obj"; - say "element->id: " . $element->id; - ok($element, "element '$element' is defined. Error captured: '$@'."); - - my $code = 'my $element = ' . '$mock_obj_' . "$obj" . '; '; - $code - .= '$t_anyone->app->repo->' . "$obj" . "_" - . "$action( " - . '$element' . " );"; - + foreach my $action ('save', 'update', 'delete') { + note "=== $obj action $action ==="; + my $code + = 'my $element = $mock_obj_' + . $obj + . '; $t_anyone->app->repo->' . "$obj" . "_" + . "$action(\$element);"; my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); ok(!$@, "eval error empty"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); } - } -foreach my $obj (@objects, @relations) { - - foreach my $action (@actions2_delete) { - note "================= Testing action $action ================="; - my $element = eval '$mock_obj_' . "$obj"; - say "element->id: " . $element->id; - ok($element, "element '$element' is defined. Error captured: '$@'."); - - my $code = 'my $element = ' . '$mock_obj_' . "$obj" . '; '; - $code - .= '$t_anyone->app->repo->' . "$obj" . "_" - . "$action( " - . '$element' . " );"; - - my $res = eval "$code"; - isnt($res, '', "'$code'. Output: '$res'. Error captured: '$@'."); - } +# Testing delete for all objects +foreach my $obj (@objects) { + my $action = "delete"; + note "=== $obj action $action ==="; + my $code + = 'my $element = $mock_obj_' + . $obj + . '; $t_anyone->app->repo->' . "$obj" . "_" + . "$action(\$element);"; + my $res = eval "$code"; + ok(!$@, "eval error empty. code: $code warn $@"); + isnt($res, undef, $obj . "_$action - output: $res should not be undef"); } ok(1); diff --git a/t/100-unit/SmartArray.t b/t/100-unit/SmartArray.t deleted file mode 100644 index 5ff32be..0000000 --- a/t/100-unit/SmartArray.t +++ /dev/null @@ -1,62 +0,0 @@ -use Test::More; -use Test::Exception; - -use Data::Dumper; -use Array::Utils qw(:all); -use feature qw( say ); -use BibSpace::Backend::SmartArray; -use BibSpace::Backend::SmartHash; -use BibSpace::Util::Thing; - -# package Thing; -# use Moose; -# has 'str' => ( is => 'rw', isa => 'Str', default => "I am a thing"); - -# package main; - -my $backend = SmartArray->new(logger => SimpleLogger->new()); - -ok($backend); - -my $thing1 = Thing->new(id => 1); -my $thing2 = Thing->new(id => 2); -my $thing3 = Thing->new(id => 3); -my $type = ref($thing1); - -ok($backend->_init(ref($thing1)), "init ok"); -ok($backend->empty($type), "empty ok"); -ok(!$backend->exists($thing1), "exists ok"); -ok($backend->_add($thing1), "add ok"); -ok($backend->exists($thing1), "exists ok"); -ok($backend->_add(($thing2, $thing3)), "add ok"); -ok($backend->all($type), "all ok"); -is($backend->count($type), 3, "count ok"); -ok(!$backend->empty($type), "empty ok"); - -ok(!$backend->find($type, sub { $_->id == 4 }), "find ok"); -ok($backend->find($type, sub { $_->id == 3 }), "find ok"); - -is(scalar($backend->filter($type, sub { $_->id < 2 })), 1, "filter ok"); -is(scalar($backend->filter($type, sub { $_->id < 3 })), 2, "filter ok"); -is(scalar($backend->filter($type, sub { $_->id < 4 })), 3, "filter ok"); -is(scalar($backend->filter($type, sub { $_->id < 100 })), 3, "filter ok"); - -$thing3->str("updated"); -ok($backend->update($thing3), "update ok"); -my $found = $backend->find($type, sub { $_->id == 3 }); -is($found->str, "updated", "found new updated object"); - -is($backend->delete($thing3), 1, "delete ok"); - -my $found = $backend->find($type, sub { $_->id == 3 }); -ok(!$found, "not found"); -is(scalar($backend->filter($type, sub { $_->id < 100 })), - 2, "filter after delete ok"); - -ok($backend->dump, "dump ok"); - -# say Dumper $backend; - -ok(1); -done_testing(); - diff --git a/t/100-unit/SmartHash.t b/t/100-unit/SmartHash.t deleted file mode 100644 index 189976c..0000000 --- a/t/100-unit/SmartHash.t +++ /dev/null @@ -1,61 +0,0 @@ -use Test::More; -use Test::Exception; - -use Data::Dumper; -use Array::Utils qw(:all); -use feature qw( say ); -use BibSpace::Backend::SmartArray; -use BibSpace::Backend::SmartHash; -use BibSpace::Util::Thing; - -# package Thing; -# use Moose; -# has 'str' => ( is => 'rw', isa => 'Str', default => "I am a thing"); - -# package main; - -my $backend = SmartHash->new(logger => SimpleLogger->new()); - -ok($backend); - -my $thing1 = Thing->new(id => 1); -my $thing2 = Thing->new(id => 2); -my $thing3 = Thing->new(id => 3); -my $type = ref($thing1); - -ok($backend->_init(ref($thing1)), "init ok"); -ok($backend->empty($type), "empty ok"); -ok(!$backend->exists($thing1), "exists ok"); -ok($backend->_add($thing1), "add ok"); -ok($backend->exists($thing1), "exists ok"); -ok($backend->_add(($thing2, $thing3)), "add ok"); -ok($backend->all($type), "all ok"); -is($backend->count($type), 3, "count ok"); -ok(!$backend->empty($type), "empty ok"); - -ok(!$backend->find($type, sub { $_->id == 4 }), "find ok"); -ok($backend->find($type, sub { $_->id == 3 }), "find ok"); - -is(scalar($backend->filter($type, sub { $_->id < 2 })), 1, "filter ok"); -is(scalar($backend->filter($type, sub { $_->id < 3 })), 2, "filter ok"); -is(scalar($backend->filter($type, sub { $_->id < 4 })), 3, "filter ok"); -is(scalar($backend->filter($type, sub { $_->id < 100 })), 3, "filter ok"); - -$thing3->str("updated"); - -ok($backend->update($thing3), "update ok"); -my $found = $backend->find($type, sub { $_->id == 3 }); -is($found->str, "updated"); - -is($backend->delete($thing3), 1, "delete ok"); -my $found = $backend->find($type, sub { $_->id == 3 }); -ok(!$found, "not found"); -is(scalar($backend->filter($type, sub { $_->id < 100 })), 2, "filter ok"); - -ok($backend->dump, "dump ok"); - -# say Dumper $backend; - -ok(1); -done_testing(); - diff --git a/t/100-unit/Tag.t b/t/100-unit/Tag.t index 92f2e65..2447032 100644 --- a/t/100-unit/Tag.t +++ b/t/100-unit/Tag.t @@ -1,12 +1,43 @@ use Mojo::Base -strict; use Test::More; use Test::Mojo; +use Test::Exception; -# my $t_anyone = Test::Mojo->new('BibSpace'); -# my $self = $t_anyone->app; +my $t_anyone = Test::Mojo->new('BibSpace'); +my $self = $t_anyone->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); +my $repo = $self->app->repo; -# use BibSpace::TestManager; -# TestManager->apply_fixture($self->app); +my @all_tags = $repo->tags_all; +ok(scalar @all_tags > 0, "There should be some tags in the system"); + +my $tag = $all_tags[0]; +ok($tag->repo, "Each tag should have ref to repo object"); +my @entries = $tag->get_entries; +ok(scalar @entries > 0, + "There should be some entries available for tag after linking"); + +ok($tag->equals($tag), "Tag is equal to itself"); +dies_ok(sub { $tag->equals(undef) }, 'Tag equals undef expecting to die'); + +my @tags_w_entries = $repo->tags_filter(sub { scalar $_->get_entries > 0 }); +ok(scalar @tags_w_entries, + "There should be some tags in the system that have entries"); + +subtest "Entry-Labeling-Tag combination" => sub { + + my $tag = $tags_w_entries[0]; + my @entries = $tag->get_entries; + my $entry = $entries[0]; + + my $labeling = $self->app->repo->entityFactory->new_Labeling( + entry_id => $entry->id, + tag_id => $tag->id + ); + + ok($entry->has_labeling($labeling), "Entry should have labeling"); + ok($tag->has_labeling($labeling), "Tag should have the same labeling"); +}; -ok(1); done_testing(); diff --git a/t/100-unit/TagType.t b/t/100-unit/TagType.t index fc89829..4487b3c 100644 --- a/t/100-unit/TagType.t +++ b/t/100-unit/TagType.t @@ -3,11 +3,21 @@ use Test::More; use Test::Mojo; use Test::Exception; -# my $t_anyone = Test::Mojo->new('BibSpace'); -# my $self = $t_anyone->app; +my $t_anyone = Test::Mojo->new('BibSpace'); +my $self = $t_anyone->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); +my $repo = $self->app->repo; -# use BibSpace::TestManager; -# TestManager->apply_fixture($self->app); +my $tt1 + = $self->app->entityFactory->new_TagType(name => 'tt1', comment => "abc"); +my $tt2 = $self->app->entityFactory->new_TagType(name => 'tt2'); +my $tt3 + = $self->app->entityFactory->new_TagType(name => 'tt1', comment => 'xyz'); + +ok($tt1->equals($tt3)); +ok(!$tt1->equals($tt2)); +ok(!$tt2->equals($tt3)); ok(1); done_testing(); diff --git a/t/100-unit/Team.t b/t/100-unit/Team.t index b888369..79a5435 100644 --- a/t/100-unit/Team.t +++ b/t/100-unit/Team.t @@ -25,6 +25,13 @@ foreach my $team (@all_teams) { note "============ Testing Team ID " . $team->id . "."; + my @memberships = $team->get_memberships; + ok( + scalar @memberships ge 0, + "Team should have 0 or more memberships and has " + . (scalar @memberships) . "." + ); + my $member_author = ($team->get_authors)[0]; my $non_member = $repo->authors_find(sub { !$_->has_team($team) }); @@ -34,25 +41,19 @@ foreach my $team (@all_teams) { isnt($team->get_membership_beginning($member_author), -1); ok(!$team->can_be_deleted, "can be deleted"); - my $mem = Membership->new( - author => $member_author->get_master, - team => $team, + my $mem = $repo->entityFactory->new_Membership( author_id => $member_author->get_master->id, team_id => $team->id ); - my $mem2 = Membership->new( - author => $member_author->get_master, - team => $team, + my $mem2 = $repo->entityFactory->new_Membership( author_id => $member_author->get_master->id, team_id => $team->id ); ## testing membership actually... - ok($mem->validate, "mem validate"); - ok($mem->equals($mem2), "mem equals"); - ok($mem->equals_id($mem2), "mem equals id"); - ok($mem->equals_obj($mem2), "mem equals obj"); + ok($mem->equals($mem2), "mem equals"); + ok($mem->equals_id($mem2), "mem equals id"); ok($member_author->update_membership($team, 0, 0), "update mem 0 0"); ok($member_author->update_membership($team, 100, 200), @@ -66,16 +67,12 @@ foreach my $team (@all_teams) { "update mem bad 100 100 pne"; if ($non_member and !$author->equals($non_member)) { - my $mem3 = Membership->new( - author => $non_member->get_master, - team => $team, + my $mem3 = $repo->entityFactory->new_Membership( author_id => $non_member->get_master->id, team_id => $team->id ); - ok($mem->validate, 'mem validate'); - ok(!$mem->equals($mem3), '!equals'); - ok(!$mem->equals_id($mem3), '!equals_id'); - ok(!$mem->equals_obj($mem3), '!equals_obj'); + ok(!$mem->equals($mem3), '!equals'); + ok(!$mem->equals_id($mem3), '!equals_id'); ok(!$team->has_membership($mem3), '!has_membership'); } diff --git a/t/100-unit/Type.t b/t/100-unit/Type.t index ee3c45b..065cbaa 100644 --- a/t/100-unit/Type.t +++ b/t/100-unit/Type.t @@ -12,10 +12,6 @@ TestManager->apply_fixture($self->app); my $repo = $self->app->repo; my @all_types = $repo->types_all; -# my $author = ($repo->authors_all)[0]; -# my $author2 = ($repo->authors_all)[1]; -# my $entry = ($repo->entries_all)[0]; - my $limit_num_tests = 20; note "============ Testing " . scalar(@all_types) . " Types ============"; @@ -23,7 +19,7 @@ note "============ Testing " . scalar(@all_types) . " Types ============"; foreach my $type (@all_types) { last if $limit_num_tests < 0; - note "============ Testing Type ID " . $type->id . "."; + note "============ Testing Type ID " . $type->our_type . "."; ok($type->equals($type), "equals"); diff --git a/t/100-unit/User.t b/t/100-unit/User.t index 69ee5e9..c9a6be7 100644 --- a/t/100-unit/User.t +++ b/t/100-unit/User.t @@ -37,6 +37,13 @@ foreach my $user (@all_users) { ok($user->make_manager, "make_manager"); ok($user->make_admin, "make_admin"); ok($user->make_user, "make_user"); + is($user->get_forgot_pass_token, undef, "get forgot_token should be undef"); + $user->set_forgot_pass_token("aabbcc"); + is($user->get_forgot_pass_token, + "aabbcc", "get forgot_token should be aabbcc"); + $user->reset_forgot_token; + is($user->get_forgot_pass_token, + undef, "get forgot_token should be undef again"); } $limit_num_tests--; diff --git a/t/200-controller/Author.t b/t/200-controller/Author.t new file mode 100644 index 0000000..d106a79 --- /dev/null +++ b/t/200-controller/Author.t @@ -0,0 +1,85 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $admin_user = Test::Mojo->new('BibSpace'); +$admin_user->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +sub aDump { + JSON->new->convert_blessed->utf8->pretty->encode(shift); +} + +my $self = $admin_user->app; +my $app_config = $admin_user->app->config; +$admin_user->ua->max_redirects(3); + +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +my @entries = $admin_user->app->repo->entries_all; +my $entry = shift @entries; +my @authors = $admin_user->app->repo->authors_all; +my $author = $authors[0]; +my @teams = $admin_user->app->repo->teams_all; +my $team = shift @teams; +my @tags = $admin_user->app->repo->tags_all; +my $tag = shift @tags; + +my @tagTypes = $admin_user->app->repo->tagTypes_all; +my $tagType = shift @tagTypes; + +my $page; + +ok($author, "There should be at least one author in the system"); +ok(scalar @authors > 1, "There should be at least one author in the system"); + +my @visible_authors + = $admin_user->app->repo->authors_filter(sub { $_->is_visible }); +ok(scalar @visible_authors > 1, + "There should be at least one visible author in the system"); +my @visible_authors_helper = $admin_user->app->get_visible_authors(); +is( + scalar @visible_authors, + scalar @visible_authors_helper, + "Helper should return the same number of visible authors as repo filter" +); +is( + scalar @visible_authors, + $admin_user->app->num_visible_authors(), + "Helper should return the same number of visible authors as repo filter" +); + +foreach my $a (@visible_authors_helper) { + ok($a, "Author should be defined"); + ok($a->id >= 1, "Author id should be >= 1"); + ok($a->get_master_id >= 1, "master_id should be >= 1"); + isnt($a->get_master, undef, "MasterObj should never be undef"); + + if ($a->is_minion) { + note "Testing a minion author"; + isnt($a->master, undef, + "Master object should not be undef. Dump: " . aDump($a)); + isnt($a->master, $a, + "Master object should not be undef. Dump: " . aDump($a)); + isnt($a->master->name, $a->uid, + "Master name should be different of minion name. Dump: " . aDump($a)); + isnt($a->get_master_id, $a->id, + "Master id should be different to minion id. Dump: " . aDump($a)); + } + else { + note "Testing a master author"; + is($a->master, $a, "Master object of master should equal to self"); + is($a->get_master_id, $a->id, "master id should point at own id"); + is($a->get_master->name, $a->name, + "Master name should be equal to own name"); + is($a->get_master->uid, $a->uid, "Master name should be equal to own name"); + } +} + +ok(1); +done_testing(); diff --git a/t/200-controller/Backup.t b/t/200-controller/Backup.t index cb27bb2..77c2bdd 100644 --- a/t/200-controller/Backup.t +++ b/t/200-controller/Backup.t @@ -39,8 +39,8 @@ subtest 'backup_do_mysql' => sub { ->status_isnt(500, "Checking: 500 $page"); }; -my $backup = do_storable_backup($self->app); -ok($backup, "found a backup"); +my $backup = do_json_backup($self->app); +ok($backup, "just made backup should be found"); $backup_id = $backup->id; subtest 'backup_do_mysql' => sub { diff --git a/t/200-controller/Login.t b/t/200-controller/Login.t index bdd6d8f..be787cb 100644 --- a/t/200-controller/Login.t +++ b/t/200-controller/Login.t @@ -4,6 +4,9 @@ use Test::Mojo; use BibSpace::Functions::MySqlBackupFunctions; +my $t_anyone = Test::Mojo->new('BibSpace'); +$t_anyone->ua->max_redirects(5); + my $t_logged_in = Test::Mojo->new('BibSpace'); $t_logged_in->post_ok( '/do_login' => {Accept => '*/*'}, @@ -15,77 +18,132 @@ $t_logged_in->ua->max_redirects(5); use BibSpace::TestManager; TestManager->apply_fixture($self->app); -$t_logged_in->post_ok( - '/register' => form => { - login => "HenryJohnTester", - name => 'Henry John', - email => 'HenryJohnTester@example.com', - password1 => 'qwerty', - password2 => 'qwerty' - } -); +subtest "Registration disabled page" => sub { + $t_anyone->app->config->{registration_enabled} = 0; + is($t_anyone->app->config->{registration_enabled}, + 0, "Registration setting should be 0"); -$t_logged_in->post_ok( - '/register' => form => { - login => "HenryJohnTester2", - name => 'Henry John2', - email => 'HenryJohnTester2@example.com', - password1 => 'qwerty2', - password2 => 'qwerty2' - } -); + $t_anyone->app->config->{registration_enabled} = 1; + is($t_anyone->app->config->{registration_enabled}, + 1, "Registration setting should be 1"); -my $me = $t_logged_in->app->repo->users_find(sub { $_->login eq 'pub_admin' }); -my $user - = $t_logged_in->app->repo->users_find(sub { $_->login eq 'HenryJohnTester' }); -my $user2 - = $t_logged_in->app->repo->users_find(sub { $_->login eq 'HenryJohnTester2' } - ); + $t_anyone->get_ok($self->url_for('registration_disabled'))->status_isnt(404) + ->status_isnt(500)->element_exists('p') + ->content_like(qr/Registration is disabled/, + "Info about disabled registration is visible"); +}; -ok($me); -ok($user); -ok($user2); - -# $logged_user->get('/profile')->to('login#profile'); -# $admin_user->get('/manage_users')->to('login#manage_users')->name('manage_users'); -# $admin_user->get('/profile/:id')->to('login#foreign_profile')->name('show_user_profile'); -# $admin_user->get('/profile/delete/:id')->to('login#delete_user')->name('delete_user'); - -# $admin_user->get('/profile/make_user/:id')->to('login#make_user')->name('make_user'); -# $admin_user->get('/profile/make_manager/:id')->to('login#make_manager')->name('make_manager'); -# $admin_user->get('/profile/make_admin/:id')->to('login#make_admin')->name('make_admin'); -# $anyone->get('/forgot')->to('login#forgot'); -# $anyone->post('/forgot/gen')->to('login#post_gen_forgot_token'); -# $anyone->get('/forgot/reset/:token')->to('login#token_clicked')->name("token_clicked"); -# $anyone->post('/forgot/store')->to('login#store_password'); -# $anyone->get('/login_form')->to('login#login_form')->name('login_form'); -# $anyone->post('/do_login')->to('login#login')->name('do_login'); -# $anyone->get('/youneedtologin')->to('login#not_logged_in')->name('youneedtologin'); -# $anyone->get('/badpassword')->to('login#bad_password')->name('badpassword'); -# $anyone->get('/logout')->to('login#logout')->name('logout'); - -my @pages = ( - '/profile', $t_logged_in->app->url_for('manage_users'), - $t_logged_in->app->url_for('show_user_profile', id => $user->id), - $t_logged_in->app->url_for('show_user_profile', id => $user2->id), - $t_logged_in->app->url_for('delete_user', id => $user2->id), # delete ok - $t_logged_in->app->url_for('make_user', id => $user->id), - $t_logged_in->app->url_for('make_manager', id => $user->id), - $t_logged_in->app->url_for('make_admin', id => $user->id), - $t_logged_in->app->url_for('delete_user', id => $user->id) - , # shall not be deleted - is admin - $t_logged_in->app->url_for('delete_user', id => $me->id) - , # shall not be deleted - is me - $t_logged_in->app->url_for('make_user', id => $me->id) - , # shall not be degraded - is me & admin -); +subtest "Registration disabled for not logged-in users" => sub { + $t_anyone->app->config->{registration_enabled} = 0; + is($t_anyone->app->config->{registration_enabled}, + 0, "Registration setting should be 0"); + + $t_anyone->get_ok($self->url_for('logout'))->status_isnt(404) + ->status_isnt(500)->element_exists('div[class$=jumbotron]') + ->content_unlike(qr/Nice to see you here/) + ->content_like(qr/Please login or register/, "Proper content after logout"); -for my $page (@pages) { - note "============ Testing page $page ============"; - $t_logged_in->get_ok($page, "Get for page $page") - ->status_isnt(404, "Checking: 404 $page") - ->status_isnt(500, "Checking: 500 $page"); -} + $t_anyone->get_ok($self->url_for('register'))->status_isnt(404) + ->status_isnt(500) + ->element_exists_not('input[name=login][type=text][value*="j.bond007"]', + "There should be no registartion form") + ->element_exists_not('input[name=email][type=text][value*="@example.com"]', + "There should be no registartion form") + ->content_like(qr/Registration is disabled/, + "Info about disabled registration is visible"); + + $t_anyone->post_ok( + $self->url_for('post_do_register') => form => { + login => "HenryJohnTester", + name => 'Henry John', + email => 'HenryJohnTester@example.com', + password1 => 'qwerty', + password2 => 'qwerty' + } + )->status_isnt(404)->status_isnt(500) + ->element_exists_not('input[name=login][type=text][value*="j.bond007"]', + "There should be no registartion form") + ->element_exists_not('input[name=email][type=text][value*="@example.com"]', + "There should be no registartion form") + ->content_like(qr/Registration is disabled/, + "Info about disabled registration is visible"); +}; + +subtest "Register users" => sub { + $t_logged_in->post_ok( + $self->url_for('post_do_register') => form => { + login => "HenryJohnTester", + name => 'Henry John', + email => 'HenryJohnTester@example.com', + password1 => 'qwerty', + password2 => 'qwerty' + } + ) + ->content_like( + qr/User created successfully! You may now login using login: HenryJohnTester./ + ); + + $t_logged_in->post_ok( + $self->url_for('post_do_register') => form => { + login => "HenryJohnTester2", + name => 'Henry John2', + email => 'HenryJohnTester2@example.com', + password1 => 'qwerty2', + password2 => 'qwerty2' + } + ) + ->content_like( + qr/User created successfully! You may now login using login: HenryJohnTester2./ + ); + + my $me + = $t_logged_in->app->repo->users_find(sub { $_->login eq 'pub_admin' }); + my $user1 + = $t_logged_in->app->repo->users_find(sub { $_->login eq 'HenryJohnTester' } + ); + my $user2 = $t_logged_in->app->repo->users_find( + sub { $_->login eq 'HenryJohnTester2' }); + + ok($me, "Admin user1 exists"); + ok($user1, "A test user (1) exists"); + ok($user2, "A test user (2) exists"); +}; + +subtest "Pages for logged-in users" => sub { + my $me + = $t_logged_in->app->repo->users_find(sub { $_->login eq 'pub_admin' }); + my $user1 + = $t_logged_in->app->repo->users_find(sub { $_->login eq 'HenryJohnTester' } + ); + my $user2 = $t_logged_in->app->repo->users_find( + sub { $_->login eq 'HenryJohnTester2' }); + + my @pages = ( + $t_logged_in->app->url_for('manage_users'), + $t_logged_in->app->url_for('show_my_profile'), + $t_logged_in->app->url_for('registration_disabled'), + $t_logged_in->app->url_for('show_user_profile', id => $user1->id), + $t_logged_in->app->url_for('show_user_profile', id => $user2->id), + $t_logged_in->app->url_for('delete_user', id => $user2->id), # delete ok + $t_logged_in->app->url_for('make_user', id => $user1->id), + $t_logged_in->app->url_for('make_manager', id => $user1->id), + $t_logged_in->app->url_for('make_admin', id => $user1->id), + $t_logged_in->app->url_for('delete_user', id => $user1->id) + , # shall not be deleted - is admin + $t_logged_in->app->url_for('delete_user', id => 99999) + , # shall not be deleted - does not exist + $t_logged_in->app->url_for('delete_user', id => $me->id) + , # shall not be deleted - is me + $t_logged_in->app->url_for('make_user', id => $me->id) + , # shall not be degraded - is me & admin + ); + + for my $page (@pages) { + note "============ Testing page $page ============"; + $t_logged_in->get_ok($page, "Get for page $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + } +}; -ok(1); done_testing(); diff --git a/t/200-controller/Persistence.t b/t/200-controller/Persistence.t index 9296603..41a04e3 100644 --- a/t/200-controller/Persistence.t +++ b/t/200-controller/Persistence.t @@ -16,29 +16,12 @@ $t_logged_in->ua->inactivity_timeout(300); use BibSpace::TestManager; TestManager->apply_fixture($self->app); -# 'system_status' -# 'load_fixture' -# 'save_fixture' -# 'copy_mysql_to_smart' -# 'copy_smart_to_mysql' -# 'persistence_status' -# 'reset_mysql' -# 'reset_smart' -# 'reset_all' -# 'insert_random_data' - my @pages = ( $t_logged_in->app->url_for('system_status'), - $t_logged_in->app->url_for('reset_smart'), - $t_logged_in->app->url_for('copy_mysql_to_smart'), - $t_logged_in->app->url_for('reset_mysql'), - $t_logged_in->app->url_for('copy_smart_to_mysql'), - $t_logged_in->app->url_for('persistence_status'), $t_logged_in->app->url_for('reset_all'), $t_logged_in->app->url_for('load_fixture'), - -# $t_logged_in->app->url_for('save_fixture'), # saving test fixture shall not be tested! - $t_logged_in->app->url_for('insert_random_data', num => 50), + $t_logged_in->app->url_for('save_fixture') + , # saving test fixture shall not be tested! ); for my $page (@pages) { diff --git a/t/200-controller/Publications.t b/t/200-controller/Publications.t index 6524501..0feecae 100644 --- a/t/200-controller/Publications.t +++ b/t/200-controller/Publications.t @@ -263,6 +263,29 @@ subtest 'add_publication_post' => sub { {new_bib => '@bad_content!', save => 1}); }; +subtest 'Add orphaned publication' => sub { + + my @orph_entries = $admin_user->app->repo->entries_filter( + sub { scalar($_->get_authors) == 0 }); + ok(scalar @orph_entries >= 0, "Some orphaned entries may exist beforehand"); + + my $random_string = random_string(16); + + my $bib_content = ' + @misc{key_2017_TEST' . $random_string . ', + publisher = {Foo Publishing house}, + title = {{Selected aspects of some methods}}, + year = {2017}, + }'; + + $admin_user->post_ok($self->url_for('add_publication_post') => form => + {new_bib => $bib_content, save => 1}); + + my @orph_entries = $admin_user->app->repo->entries_filter( + sub { scalar($_->get_authors) == 0 }); + ok(scalar @orph_entries > 0, "Some orphaned entries exist"); +}; + # generated with: ./bin/bibspace routes | grep GET | grep -v : my @pages = ( $self->url_for('publications'), $self->url_for('recently_added', num => 10), @@ -322,8 +345,9 @@ my @pages = ( $self->url_for('manage_attachments', id => 0), $self->url_for('manage_attachments', id => $entry->id), - $self->url_for('fix_attachment_urls', id => 0), - $self->url_for('fix_attachment_urls', id => $entry->id), + $self->url_for('fix_attachment_urls'), + $self->url_for('fix_attachment_urls')->query(id => 0), + $self->url_for('fix_attachment_urls')->query(id => $entry->id), $self->url_for('manage_exceptions', id => 0), $self->url_for('manage_exceptions', id => $entry->id), diff --git a/t/200-controller/TagTypes.t b/t/200-controller/TagTypes.t new file mode 100644 index 0000000..083cfb8 --- /dev/null +++ b/t/200-controller/TagTypes.t @@ -0,0 +1,104 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $admin_user = Test::Mojo->new('BibSpace'); +$admin_user->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +my $self = $admin_user->app; +my $app_config = $admin_user->app->config; +$admin_user->ua->max_redirects(3); + +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +subtest "Add new tag type" => sub { + + my $selector1 = 'span[class$=tag-type-name tag-type-name-test-tag-type-1]'; + my $selector2 + = 'span[class$=tag-type-name tag-type-name-tag_type_with_whitespace]'; + + $admin_user->post_ok( + $self->url_for('add_tag_type_post') => form => { + new_name => "test-tag-type-1", + new_comment => "testing adding tag types" + } + )->element_exists($selector1)->text_like($selector1 => qr/test-tag-type-1/); + + $admin_user->post_ok( + $self->url_for('add_tag_type_post') => form => { + new_name => "tag type with whitespace", + new_comment => "testing adding tag types with whitespaces" + } + )->element_exists($selector2) + ->text_like($selector2 => qr/tag type with whitespace/); +}; + +subtest "Attempt Delete protected tag type" => sub { + + my $selector1 = 'span[class$=tag-type-id tag-type-id-1]'; + my $selector2 = 'span[class$=tag-type-id tag-type-id-2]'; + + # this should be verb: DELETE + $admin_user->get_ok($self->url_for('delete_tag_type', id => 1)) + ->element_exists($selector1)->text_like($selector1 => qr/1/) + ->element_exists($selector2)->text_like($selector2 => qr/2/) + ->element_exists('div[class$=alert alert-warning]') + ->text_like('div[class$=alert alert-warning]' => + qr/Tag Types 1 or 2 are essential and cannot be deleted/); + + $admin_user->get_ok($self->url_for('delete_tag_type', id => 2)) + ->element_exists($selector1)->text_like($selector1 => qr/1/) + ->element_exists($selector2)->text_like($selector2 => qr/2/) + ->element_exists('div[class$=alert alert-warning]') + ->text_like('div[class$=alert alert-warning]' => + qr/Tag Types 1 or 2 are essential and cannot be deleted/); +}; + +subtest "Delete regular tag type" => sub { + + my $tt1 + = $self->app->repo->tagTypes_find(sub { $_->name eq "test-tag-type-1" }); + ok($tt1, "Found a tag-type object"); + + # this should be verb: DELETE + my $selector = 'span[class$=tag-type-id tag-type-id-' . $tt1->id . ']'; + + $admin_user->get_ok($self->url_for('delete_tag_type', id => $tt1->id)) + ->element_exists_not($selector) + ->element_exists('div[class$=alert alert-success]') + ->text_like('div[class$=alert alert-success]' => qr/Tag type deleted/); +}; + +subtest "Edit regular tag type" => sub { + + my $tt1 = $self->app->repo->tagTypes_find( + sub { $_->name eq "tag type with whitespace" }); + ok($tt1, "Found a tag-type object"); + my $tt1_id = $tt1->id; + + # this should be verb: PUT + # GET + $admin_user->get_ok($self->url_for('edit_tag_type', id => $tt1->id)) + ->text_like('label' => qr/Edit tag type/) + ->element_exists('input[id$=new_name]') + ->element_exists('input[id$=new_comment]'); + + # POST + my $selector_id = 'span[class$=tag-type-id tag-type-id-' . $tt1->id . ']'; + my $selector_name = 'span[class$=tag-type-name tag-type-name-fizz-buzz]'; + + $admin_user->post_ok( + $self->url_for('edit_tag_type', id => $tt1->id) => form => + {new_name => "fizz-buzz", new_comment => "comment-fizz-buzz"}) + ->element_exists($selector_id)->element_exists($selector_name) + ->text_like($selector_id => qr/$tt1_id/) + ->text_like($selector_name => qr/fizz-buzz/); +}; + +done_testing(); diff --git a/t/200-controller/Tags.t b/t/200-controller/Tags.t index d3b7378..57cb5cc 100644 --- a/t/200-controller/Tags.t +++ b/t/200-controller/Tags.t @@ -17,14 +17,6 @@ $admin_user->ua->max_redirects(3); use BibSpace::TestManager; TestManager->apply_fixture($self->app); -# $logged_user->get('/tags/:type')->to( 'tags#index', type => 1 )->name('all_tags'); -# $admin_user->get('/tags/add/:type')->to( 'tags#add', type => 1 )->name('add_tag_get'); -# $admin_user->post('/tags/add/:type')->to( 'tags#add_post', type => 1 )->name('add_tag_post'); -# $logged_user->get('/tags/authors/:tid/:type')->to( 'tags#get_authors_for_tag', type => 1 ) -# ->name('get_authors_for_tag'); -# $admin_user->get('/tags/delete/:id')->to('tags#delete')->name('delete_tag'); -# $manager_user->get('/tags/edit/:id')->to('tags#edit')->name('edit_tag'); - my @entries = $admin_user->app->repo->entries_all; my $entry = shift @entries; my $author = ($admin_user->app->repo->authors_all)[0]; @@ -65,7 +57,6 @@ foreach my $type (@tagTypes) { $admin_user->post_ok( $self->url_for('add_tag_post', type => $typeID) => form => {new_tag => "zz_$typeID;aa_$typeID;;test_tag2_type_$typeID"}); - } foreach my $tag (@tags) { @@ -94,7 +85,6 @@ foreach my $tag (@tags) { $admin_user->get_ok($page, "Get for page $page") ->status_isnt(404, "Checking: 404 $page") ->status_isnt(500, "Checking: 500 $page"); - } my $tag_del = shift @tags; @@ -105,8 +95,5 @@ $admin_user->get_ok($page, "Get for page $page") ->status_isnt(404, "Checking: 404 $page") ->status_isnt(500, "Checking: 500 $page"); -# subtest 'aaa' => sub { -# }; - ok(1); done_testing(); diff --git a/t/200-controller/Teams.t b/t/200-controller/Teams.t new file mode 100644 index 0000000..8076c98 --- /dev/null +++ b/t/200-controller/Teams.t @@ -0,0 +1,134 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $admin_user = Test::Mojo->new('BibSpace'); +$admin_user->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +my $self = $admin_user->app; +my $app_config = $admin_user->app->config; +$admin_user->ua->max_redirects(3); + +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +my @entries = $admin_user->app->repo->entries_all; +my $entry = shift @entries; +my $author = ($admin_user->app->repo->authors_all)[0]; +my @teams = $admin_user->app->repo->teams_all; +my $team = shift @teams; +my @tags = $admin_user->app->repo->tags_all; +my $tag = shift @tags; + +subtest "Read operations on teams" => sub { + my $page; + + $page = $self->url_for('all_teams'); + note "============ Testing page $page ============"; + $admin_user->get_ok($page, "Get for page $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + + $page = $self->url_for('add_team_get'); + note "============ Testing page $page ============"; + $admin_user->get_ok($page, "Get for page $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + +# Not-existing team should return 404 + $page = $self->url_for('edit_team', id => -1); + note "============ Testing page $page ============"; + +# This does not return 404 if not found. +# It redirects to the previous page (Status 301 - but gets 200 due to allow_redirects) and displays error buble + $admin_user->get_ok($page, "Get for page $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); +}; + +subtest "Edit on teams" => sub { + my $page; + foreach my $team (@teams) { + + $page = $self->url_for('edit_team', id => $team->id); + note "============ Testing page $page ============"; + $admin_user->get_ok($page, "Get for page $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + + $page = $self->url_for('unrelated_papers_for_team', teamid => $team->id); + note "============ Testing page $page ============"; + $admin_user->get_ok($page, "Get for page $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + } +}; + +subtest "Add team, Add duplicate team, Delete team" => sub { + $admin_user->post_ok( + $self->url_for('add_team_post') => form => {new_team => "test-team-1"}); + + # Try to add another team which name already exists + $admin_user->post_ok( + $self->url_for('add_team_post') => form => {new_team => "test-team-1"}); + + $admin_user->post_ok( + $self->url_for('add_team_post') => form => {new_team => "test-team-2"}); + + my $team1 = $self->app->repo->teams_find(sub { $_->name eq "test-team-1" }); + my $team2 = $self->app->repo->teams_find(sub { $_->name eq "test-team-2" }); + + # FIXME: delete is done with verb GET but should be with DELETE + $admin_user->get_ok($self->url_for('delete_team', id => $team1->id)); + + # FIXME: delete is done with verb GET but should be with DELETE + $admin_user->get_ok($self->url_for('delete_team_force', id => $team2->id)); +}; + +subtest "Delete team that has members" => sub { + my $team1 = $self->app->repo->teams_find(sub { $_->get_members gt 0 }); + ok($team1, "Team having members should exist"); + my @members = $team->get_members; + my $member = $members[0]; + note "Using team " + . $team1->name + . " that has " + . scalar @members + . " members. The first one is named: " + . $member->name; + ok($member->name, "A random member should have name some name."); + + my @author_teams = $member->get_teams; + my $num_teams = scalar @author_teams; + note "Author " . $member->name . " has " . $num_teams . " memberships"; + my $found_team = grep { $_->name eq $team1->name } @author_teams; + ok($found_team, "Author should be member of the original team"); + + my @memberships = $team->get_memberships; + note "Team mamberships: " . join(" ", @memberships); + + note "Deleting team " . $team1->name; + $admin_user->get_ok($self->url_for('delete_team_force', id => $team1->id)); + + my @author_teams = $member->get_teams; + my $num_teams_post_delete = scalar @author_teams; + note "Author " + . $member->name . " has " + . $num_teams_post_delete + . " memberships"; + is( + $num_teams_post_delete, + $num_teams - 1, + "Author should be member in one team less" + ); + + my $found_team = grep { $_->name eq $team1->name } @author_teams; + ok(!$found_team, "Author should be member of the original team"); +}; + +done_testing(); diff --git a/t/300-functions/BackupFunctions.t b/t/300-functions/BackupFunctions.t index 27f604a..d607136 100644 --- a/t/300-functions/BackupFunctions.t +++ b/t/300-functions/BackupFunctions.t @@ -58,11 +58,7 @@ SKIP: { my $some_backup = Backup->parse("" . $json_paths[0]); my $jsonString = path($json_paths[0])->slurp_utf8; my $dto = BibSpaceDTO->new(); - my $decodedDTO = $dto->toLayeredRepo( - $jsonString, - $t_logged_in->app->repo->lr->uidProvider, - $t_logged_in->app->repo->lr->preferences - ); + my $decodedDTO = $dto->toLayeredRepo($jsonString, $t_logged_in->app->repo); ok($decodedDTO); # this doesn't test much... ok( scalar $decodedDTO->get('Entry') > 0, diff --git a/t/900-frontend/002_basic_gets.t b/t/900-frontend/002_basic_gets.t index 01e3fd6..052f0b3 100644 --- a/t/900-frontend/002_basic_gets.t +++ b/t/900-frontend/002_basic_gets.t @@ -41,10 +41,12 @@ my $some_author = $authors[0]; # generated with: ./bin/bibspace routes | grep GET | grep -v : my @pages = ( - $self->url_for('start'), $self->url_for('system_status'), "/forgot", - "/login_form", "/youneedtologin", "/badpassword", "/register", "/log", - - $self->url_for('add_publication'), $self->url_for('add_many_publications'), + $self->url_for('start'), $self->url_for('system_status'), + $self->url_for('forgot_password'), $self->url_for('show_stats'), + $self->url_for('login_form'), $self->url_for('youneedtologin'), + $self->url_for('badpassword'), $self->url_for('register'), + $self->url_for('show_log'), $self->url_for('add_publication'), + $self->url_for('add_many_publications'), $self->url_for('recently_changed', num => 10), $self->url_for('recently_added', num => 10), @@ -52,20 +54,15 @@ my @pages = ( $self->url_for('fix_attachment_urls'), $self->url_for('clean_ugly_bibtex'), $self->url_for('regenerate_html_for_all'), - $self->url_for('fix_masters'), - "/profile", "/backups", "/types", "/types/add", "/authors", "/authors/add", "/authors/reassign", "/authors/reassign_and_create", "/tagtypes", "/tagtypes/add", "/teams", "/teams/add", "/publications", $self->url_for('all_orphaned'), "/publications/candidates_to_delete", - "/publications/missing_month", "/read/publications/meta", "/cron", - "/cron/day", "/cron/night", "/cron/week", "/cron/month", "/logout", + "/publications/missing_month", "/read/publications/meta", "/logout", ); # Logout must be last! -# Logout must be last! - my $ws_page; $ws_page = $self->url_for('show_stats_websocket', num => 10); $t_logged_in->websocket_ok($ws_page, "Websocket OK for $ws_page"); diff --git a/t/900-frontend/003_login.t b/t/900-frontend/003_login.t index 49db7ff..260442d 100644 --- a/t/900-frontend/003_login.t +++ b/t/900-frontend/003_login.t @@ -10,6 +10,7 @@ my $t_anyone = Test::Mojo->new('BibSpace'); my $self = $t_anyone->app; use BibSpace::TestManager; TestManager->apply_fixture($self->app); +$self->preferences->run_in_demo_mode(0); subtest 'login: start page asking to login' => sub { $t_anyone->get_ok('/')->status_is(200); diff --git a/t/900-frontend/004_landing_publications.t b/t/900-frontend/004_landing_publications.t index 18b645f..e389731 100644 --- a/t/900-frontend/004_landing_publications.t +++ b/t/900-frontend/004_landing_publications.t @@ -22,22 +22,29 @@ $t_logged_in->ua->max_redirects(3); my @all_tag_type_objs = $t_logged_in->app->repo->tagTypes_all; my $some_tag_type_obj = $all_tag_type_objs[0]; +BAIL_OUT("Example tag_type must exist for this test") if not $some_tag_type_obj; my @tags = $t_logged_in->app->repo->tags_filter(sub { scalar($_->get_entries) > 0 }); my $some_tag = $tags[0]; +BAIL_OUT("Example tag (one that has some entries) must exist for this test") + if not $some_tag; my @tags_permalink = $t_logged_in->app->repo->tags_filter( sub { defined $_->permalink and length($_->permalink) > 1 }); my $some_permalink = $tags_permalink[0]; +BAIL_OUT("Example permalink must exist for this test") if not $some_permalink; my @teams = $t_logged_in->app->repo->teams_all; my $some_team = $teams[0]; +BAIL_OUT("Example team must exist for this test") if not $some_team; my @authors = $t_logged_in->app->repo->authors_filter(sub { scalar($_->get_entries) > 0 } ); my $some_author = $authors[0]; +BAIL_OUT("Example author (having entries) must exist for this test") + if not $some_author; # generated with: ./bin/bibspace routes | grep GET | grep -v : my @pages = ( diff --git a/t/900-frontend/004_publications_single.t b/t/900-frontend/004_publications_single.t index 5c1294c..2ce0105 100644 --- a/t/900-frontend/004_publications_single.t +++ b/t/900-frontend/004_publications_single.t @@ -26,15 +26,13 @@ TestManager->apply_fixture($self->app); subtest 'Checking pages for single publication' => sub { - # local $TODO = "Testing gets for single publications"; - - my @entries = $t_logged_in->app->repo->entries_all; - my $size = scalar(@entries); + my @all_entries = $t_logged_in->app->repo->entries_all; + my $size = scalar(@all_entries); $num_publications_limit = $size if $size < $num_publications_limit; my $num_done = 0; - while ($num_done < $num_publications_limit) { - my $e = $entries[$num_done]; + for my $e (@all_entries) { + last if $num_done >= $num_publications_limit; my $id = $e->id; my @pages = ( @@ -46,7 +44,6 @@ subtest 'Checking pages for single publication' => sub { $self->url_for('manage_tags', id => $id), $self->url_for('manage_exceptions', id => $id), $self->url_for('show_authors_of_entry', id => $id), - $self->url_for('metalist_entry', id => $id), ); for my $page (@pages) { note "============ Testing page $page for paper id $id ============"; @@ -59,6 +56,28 @@ subtest 'Checking pages for single publication' => sub { } }; +subtest 'Checking meta list for visible publications' => sub { + my @visible_entries + = $t_logged_in->app->repo->entries_filter(sub { not $_->is_hidden }); + for my $e (@visible_entries) { + my $page = $self->url_for('metalist_entry', id => $e->id); + $t_logged_in->get_ok($page, "Checking: OK $page") + ->status_isnt(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + } +}; + +subtest 'Checking meta list for invisible publications' => sub { + my @hidden_entries + = $t_logged_in->app->repo->entries_filter(sub { $_->is_hidden }); + for my $e (@hidden_entries) { + my $page = $self->url_for('metalist_entry', id => $e->id); + $t_logged_in->get_ok($page, "Checking: OK $page") + ->status_is(404, "Checking: 404 $page") + ->status_isnt(500, "Checking: 500 $page"); + } +}; + ok(1); done_testing(); diff --git a/t/900-frontend/0051_authors_merge.t b/t/900-frontend/0051_authors_merge.t new file mode 100644 index 0000000..921d73a --- /dev/null +++ b/t/900-frontend/0051_authors_merge.t @@ -0,0 +1,101 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $op = Test::Mojo->new('BibSpace'); +my $self = $op->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +$op->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +$self = $op->app; +my $app_config = $op->app->config; +$op->ua->max_redirects(10); + +subtest '(fixture) Add merge authors using author IDs' => sub { + my $author_from + = $op->app->repo->authors_find(sub { $_->name eq 'TestMinion' }); + my $author_to + = $op->app->repo->authors_find(sub { $_->name eq 'TestMaster' }); + ok($author_from); + ok($author_to); + my $aname_from = $author_from->name; + my $aname_to = $author_to->name; + + note "Merging TestMinion into TestMaster"; + $op->post_ok( + $self->url_for('merge_authors') => {Accept => '*/*'}, + form => {author_to => $author_to->id, author_from => $author_from->id} + )->status_is(200)->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/TestMaster/) + ->content_like(qr/\Q Author TestMinion was merged into TestMaster/); + + my $edit_get_url = $self->url_for('edit_author', id => $author_to->id); + $op->get_ok($edit_get_url)->status_is(200) + ->element_exists('a[class$=author-minor-name-TestMinion]') + ->text_like('a[class$=author-minor-name-TestMinion]' => qr/TestMinion/) + ->element_exists('a[class$=author-minor-name-TestMaster]') + ->text_like('a[class$=author-minor-name-TestMaster]' => qr/TestMaster/); + + my @entries_master = $author_to->get_entries; + my @entries_minion = $author_from->get_entries; + + # 3 is a magic number calculated from fixture data + # master and minion have 1 common paper, and 1 separate paper each - total is 3 + is(scalar @entries_master, + 3, "Master author should have 3 entries after merging"); + is(scalar @entries_minion, + 0, "Minion author should have 0 entries after merging"); + +}; + +subtest 'Edit author remove minion (unlink)' => sub { + my $author = $op->app->repo->authors_find(sub { $_->name eq 'TestMaster' }); + ok($author->id > 1); + my $minion = $op->app->repo->authors_find(sub { $_->name eq 'TestMinion' }); + ok($minion->id > 1); + + my $url = $self->url_for( + 'remove_author_uid', + master_id => $author->id, + minor_id => $minion->id + ); + $op->get_ok($url)->status_is(200); + + my $edit_get_url_master = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url_master)->status_is(200) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/TestMaster/) + ->element_exists('a[class$=author-minor-name-TestMaster]') + ->text_like('a[class$=author-minor-name-TestMaster]' => qr/TestMaster/) + ->element_exists_not('a[class$=author-minor-name-TestMinion]'); + + my $edit_get_url_minion = $self->url_for('edit_author', id => $minion->id); + $op->get_ok($edit_get_url_minion)->status_is(200) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/TestMinion/) + ->element_exists('a[class$=author-minor-name-TestMinion]') + ->text_like('a[class$=author-minor-name-TestMinion]' => qr/TestMinion/) + ->element_exists_not('a[class$=author-minor-name-TestMaster]'); + + # TODO: run reassign? + + my @entries_master = $author->get_entries; + my @entries_minion = $minion->get_entries; + + # 2 are magic numbers calculated from fixture data + # master and minion have 1 common paper, and 1 separate paper each - total is 3 + is(scalar @entries_master, + 2, "Master author should have 3 entries after merging"); + is(scalar @entries_minion, + 2, "Minion author should have 0 entries after merging"); + +}; + +done_testing(); diff --git a/t/900-frontend/005_authors.t b/t/900-frontend/005_authors.t new file mode 100644 index 0000000..ee0817f --- /dev/null +++ b/t/900-frontend/005_authors.t @@ -0,0 +1,308 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $op = Test::Mojo->new('BibSpace'); +my $self = $op->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +$op->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +$self = $op->app; +my $app_config = $op->app->config; +$op->ua->max_redirects(10); + +my @teams = $op->app->repo->teams_all; +my $some_team = $teams[0]; + +my @authors + = $op->app->repo->authors_filter(sub { scalar($_->get_entries) > 0 }); +my $some_author = $authors[0]; + +subtest + '(fixture) Master authors (7) should include visible (6) and invisible (1) authors' + => sub { + $op->get_ok($self->url_for('all_authors'))->status_is(200) + ->content_like(qr/ExampleJohny/i)->content_like(qr/InvisibleHenry/i); + +# Matches only first row of the table +# ->text_like('td p:only-child[class$=author-master-name]' => qr/InvisibleHenry/) + + $op->get_ok($self->url_for('all_authors')->query(visible => 1)) + ->status_is(200)->content_like(qr/ExampleJohny/i) + ->content_unlike(qr/InvisibleHenry/i); + + $op->get_ok($self->url_for('all_authors')->query(visible => 0)) + ->status_is(200) + ->text_like( + 'td p:only-child[class$=author-master-name]' => qr/InvisibleHenry/) + ->text_unlike( + 'td p:only-child[class$=author-master-name]' => qr/ExampleJohny/); + + $op->get_ok($self->url_for('all_authors')->query(visible => 0, letter => 'I')) + ->status_is(200) + ->text_like( + 'td p:only-child[class$=author-master-name]' => qr/InvisibleHenry/) + ->text_unlike( + 'td p:only-child[class$=author-master-name]' => qr/ExampleJohny/); + + $op->get_ok($self->url_for('all_authors')->query(visible => 0, letter => 'E')) + ->status_is(200) + ->text_unlike( + 'td p:only-child[class$=author-master-name]' => qr/InvisibleHenry/) + ->text_unlike( + 'td p:only-child[class$=author-master-name]' => qr/ExampleJohny/); + + $op->get_ok($self->url_for('all_authors')->query(visible => 1, letter => 'E')) + ->status_is(200) + ->text_unlike( + 'td p:only-child[class$=author-master-name]' => qr/InvisibleHenry/) + ->text_like( + 'td p:only-child[class$=author-master-name]' => qr/ExampleJohny/); + }; + +subtest '(fixture) Check shortcut for first letters of authors' => sub { + $op->get_ok($self->url_for('all_authors')->query(visible => 1)) + ->status_is(200)->element_exists('ul li a[class$=author-letter-E]') + ->element_exists_not('ul li a[class$=author-letter-I]'); + + $op->get_ok($self->url_for('all_authors')->query(visible => 0)) + ->status_is(200)->element_exists('ul li a[class$=author-letter-I]') + ->element_exists_not('ul li a[class$=author-letter-E]'); +}; + +subtest 'Add author post' => sub { + + $op->post_ok( + $self->url_for('add_author') => {Accept => '*/*'}, + form => {new_master => 'XTestMasterAutogen'} + )->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen/); + + $op->post_ok( + $self->url_for('add_author') => {Accept => '*/*'}, + form => {new_master => 'XTestMasterAutogen'} + ) + ->content_like( + qr/Author with proposed master: XTestMasterAutogen already exists!/); +}; + +subtest '(fixture) Add author to team' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + my $aid = $author->id; + my $aname = $author->name; + my $tname = $some_team->name; + + my $url = $self->url_for( + 'add_author_to_team', + id => $author->id, + tid => $some_team->id + ); + $op->get_ok($url)->status_is(200) + ->content_like(qr/\QAuthor $aname has just joined team $tname/) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen/) + ->text_like('span[class$=author-id]' => qr/\Q$aid/); +}; + +subtest '(fixture) Edit author membership dates' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + my $aid = $author->id; + my $aname = $author->name; + my $tname = $some_team->name; + + my $edit_get_url = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url)->status_is(200) + ->element_exists('span[class$=author-joined-team-year-0]') + ->element_exists('span[class~=author-joined-team-id-1]') + ->element_exists('span[class$=author-left-team-year-inf]') + ->element_exists('span[class~=author-left-team-id-1]'); + + $op->post_ok( + $self->url_for('edit_author_membership_dates') => {Accept => '*/*'}, + form => + {aid => $aid, tid => $some_team->id, new_start => 2000, new_stop => 2018} + )->status_is(200)->content_like(qr/Membership updated successfully/) + ->element_exists('span[class$=author-joined-team-year-2000]') + ->element_exists('span[class~=author-joined-team-id-1]') + ->element_exists('span[class$=author-left-team-year-2018]') + ->element_exists('span[class~=author-left-team-id-1]'); +}; + +subtest '(fixture) Remove author from team' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + my $aid = $author->id; + my $aname = $author->name; + + $op->get_ok( + $self->url_for( + 'remove_author_from_team', + id => $author->id, + tid => $some_team->id + ) + )->status_is(200)->content_like(qr/\QAuthor $aname has just left team/) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen/); +}; + +subtest '(fixture) Toggle author visibility' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + + # Make invisible + my $old_visibility = $author->display; + $author->display(0); + + $op->get_ok($self->url_for('all_authors')->query(visible => 0, letter => 'X')) + ->status_is(200)->element_exists('ul li a[class$=author-letter-X]') + ->text_like( + 'td p:only-child[class$=author-master-name]' => qr/XTestMasterAutogen/); + + $op->get_ok($self->url_for('toggle_author_visibility', id => $author->id)) + ->status_is(200); + + $op->get_ok($self->url_for('all_authors')->query(visible => 1, letter => 'X')) + ->status_is(200)->element_exists('ul li a[class$=author-letter-X]') + ->text_like( + 'td p:only-child[class$=author-master-name]' => qr/XTestMasterAutogen/); + + $op->get_ok($self->url_for('toggle_author_visibility', id => $author->id)) + ->status_is(200); + + $op->get_ok($self->url_for('all_authors')->query(visible => 0, letter => 'X')) + ->status_is(200)->element_exists('ul li a[class$=author-letter-X]') + ->text_like( + 'td p:only-child[class$=author-master-name]' => qr/XTestMasterAutogen/); + + $author->display($old_visibility); +}; + +subtest 'Edit author post add user_id (name)' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + my $aid = $author->id; + + $op->post_ok( + $self->url_for('edit_author') => {Accept => '*/*'}, + form => {id => $author->id, new_user_id => 'XTestMasterAutogen2'} + )->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen/); + + $op->post_ok( + $self->url_for('edit_author') => {Accept => '*/*'}, + form => {id => $author->id, new_user_id => 'XTestMasterAutogen2'} + ) + ->content_like( + qr/Cannot add user ID XTestMasterAutogen2. Such ID already exist. Maybe you want to merge authors instead?/ + ); + + my $edit_get_url = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url)->status_is(200) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen/) + ->element_exists('a[class$=author-minor-name-XTestMasterAutogen2]') + ->text_like('a[class$=author-minor-name-XTestMasterAutogen2]' => + qr/XTestMasterAutogen2/); +}; + +subtest 'Edit author remove minion (unlink)' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + my $minion + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen2' }); + ok($minion->id > 1); + + my $url = $self->url_for( + 'remove_author_uid', + master_id => $author->id, + minor_id => $minion->id + ); + $op->get_ok($url)->status_is(200); + + my $edit_get_url = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url)->status_is(200) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen/) + ->element_exists('a[class$=author-minor-name-XTestMasterAutogen]') + ->text_like( + 'a[class$=author-minor-name-XTestMasterAutogen]' => qr/XTestMasterAutogen/) + ->element_exists_not('a[class$=author-minor-name-XTestMasterAutogen3]'); +}; + +subtest 'Edit author post change master name' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + + $op->post_ok( + $self->url_for('edit_author') => {Accept => '*/*'}, + form => {id => $author->id, new_master => 'XTestMasterAutogen3'} + )->status_is(200) + ->content_like(qr/Master name has been updated successfully/); + + my $edit_get_url = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url)->status_is(200) + ->element_exists('h1[class$=author-master-name]') + ->text_like('h1[class$=author-master-name]' => qr/XTestMasterAutogen3/); + + $op->post_ok( + $self->url_for('edit_author') => {Accept => '*/*'}, + form => {id => $author->id, new_master => 'XTestMasterAutogen3'} + )->status_is(200)->content_like(qr/This master name is already taken/); + + # Return to the original master name + $op->post_ok( + $self->url_for('edit_author') => {Accept => '*/*'}, + form => {id => $author->id, new_master => 'XTestMasterAutogen'} + )->status_is(200) + ->content_like(qr/Master name has been updated successfully/); +}; + +subtest 'Edit author delete' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen' }); + ok($author->id > 1); + my $aid = $author->id; + my $aname = $author->name; + + my $delete_url = $self->url_for('delete_author', id => $author->id); + $op->get_ok($delete_url)->status_is(200) + ->content_like(qr/\QAuthor $aname ID $aid has been removed successfully/); + + my $edit_get_url = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url)->status_is(200) + ->content_like(qr/\QAuthor with id $aid does not exist!/); +}; + +subtest 'Edit author delete force' => sub { + my $author + = $op->app->repo->authors_find(sub { $_->name eq 'XTestMasterAutogen2' }); + ok($author->id > 1); + my $aid = $author->id; + my $aname = $author->name; + + my $delete_url = $self->url_for('delete_author_force', id => $author->id); + $op->get_ok($delete_url)->status_is(200) + ->content_like(qr/\QAuthor $aname ID $aid has been removed successfully/); + + my $edit_get_url = $self->url_for('edit_author', id => $author->id); + $op->get_ok($edit_get_url)->status_is(200) + ->content_like(qr/\QAuthor with id $aid does not exist!/); +}; + +done_testing(); diff --git a/t/900-frontend/006_types.t b/t/900-frontend/006_types.t new file mode 100644 index 0000000..1ef89c6 --- /dev/null +++ b/t/900-frontend/006_types.t @@ -0,0 +1,164 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $op = Test::Mojo->new('BibSpace'); +my $self = $op->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +$op->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +$self = $op->app; +my $app_config = $op->app->config; +$op->ua->max_redirects(10); + +my @tts = $op->app->repo->tagTypes_all; +my $some_tt = $tts[0]; + +subtest 'Show all types' => sub { + my $page = $self->url_for('all_types'); + $op->get_ok($page)->status_is(200)->status_isnt(404)->status_isnt(500) + ->element_exists('p[class$=type-name-article]') + ->text_like('p[class$=type-name-article]' => qr/article/); +}; + +subtest 'Manage type' => sub { + my $page = $self->url_for('edit_type', name => 'article'); + $op->get_ok($page, "Get for page $page")->status_is(200)->status_isnt(404) + ->status_isnt(500); +}; + +subtest 'Add new our type' => sub { + $op->post_ok( + $self->url_for('add_type_post') => {Accept => '*/*'}, + form => {new_type => 'XTestType'} + )->element_exists('p[class$=type-name-XTestType]') + ->text_like('p[class$=type-name-XTestType]' => qr/XTestType/); +}; + +subtest 'Update type description' => sub { + $op->post_ok( + $self->url_for('update_type_description') => {Accept => '*/*'}, + form => {our_type => 'XTestType', new_description => "This is test type"} + )->element_exists('input[class$=type-description]') + ->element_exists( + 'input[class$=type-description][value=\'This is test type\']'); + + my $page = $self->url_for('edit_type', name => 'XTestType'); + $op->get_ok($page)->status_is(200) + ->element_exists('input[class$=type-description]') + ->element_exists( + 'input[class$=type-description][value=\'This is test type\']'); +}; + +subtest 'Update type mapping' => sub { + my $page = $self->url_for( + 'map_bibtex_type', + our_type => 'XTestType', + bibtex_type => 'article' + ); + $op->get_ok($page)->status_is(200) + ->element_exists('span[class$=type-mapped-article]') + ->element_exists('span[class$=type-mapped-dummy]'); + + my $page_edit = $self->url_for('edit_type', name => 'XTestType'); + $op->get_ok($page_edit)->status_is(200) + ->element_exists('span[class$=type-mapped-article]') + ->element_exists('span[class$=type-mapped-dummy]'); +}; + +subtest 'Remove legal type mapping' => sub { + my $page = $self->url_for( + 'unmap_bibtex_type', + our_type => 'XTestType', + bibtex_type => 'article' + ); + $op->get_ok($page)->status_is(200) + ->element_exists_not('span[class$=type-mapped-article]') + ->element_exists('span[class$=type-mapped-dummy]'); + + my $page_edit = $self->url_for('edit_type', name => 'XTestType'); + $op->get_ok($page_edit)->status_is(200) + ->element_exists_not('span[class$=type-mapped-article]') + ->element_exists('span[class$=type-mapped-dummy]'); +}; + +subtest 'Remove dummy type mapping' => sub { + my $map_page = $self->url_for( + 'map_bibtex_type', + our_type => 'XTestType', + bibtex_type => 'article' + ); + $op->get_ok($map_page)->status_is(200) + ->element_exists('span[class$=type-mapped-article]') + ->element_exists('span[class$=type-mapped-dummy]'); + + my $unmap_page = $self->url_for( + 'unmap_bibtex_type', + our_type => 'XTestType', + bibtex_type => 'dummy' + ); + $op->get_ok($unmap_page)->status_is(200) + ->element_exists('span[class$=type-mapped-article]') + ->element_exists_not('span[class$=type-mapped-dummy]'); + + my $page_edit = $self->url_for('edit_type', name => 'XTestType'); + $op->get_ok($page_edit)->status_is(200) + ->element_exists('span[class$=type-mapped-article]') + ->element_exists_not('span[class$=type-mapped-dummy]'); +}; + +subtest 'Remove all mappings' => sub { + my $unmap_page = $self->url_for( + 'unmap_bibtex_type', + our_type => 'XTestType', + bibtex_type => 'dummy' + ); + $op->get_ok($unmap_page)->status_is(200) + ->element_exists_not('span[class$=type-mapped-dummy]'); + +# This will crash, because we cannot remove all mappings +# TODO: This shouldn't crash - there should be a reasonable message explaining user why the last mapping cannot be removed + my $unmap_page2 = $self->url_for( + 'unmap_bibtex_type', + our_type => 'XTestType', + bibtex_type => 'article' + ); + $op->get_ok($unmap_page2)->status_is(500) + ->element_exists_not('span[class$=type-mapped-dummy]'); +}; + +subtest 'Remove type' => sub { + $op->post_ok( + $self->url_for('add_type_post') => {Accept => '*/*'}, + form => {new_type => 'XTestType'} + )->element_exists('p[class$=type-name-XTestType]') + ->text_like('p[class$=type-name-XTestType]' => qr/XTestType/); + + my $all_page = $self->url_for('all_types'); + $op->get_ok($all_page)->status_is(200)->status_isnt(404) + ->element_exists('p[class$=type-name-XTestType]') + ->text_like('p[class$=type-name-XTestType]' => qr/XTestType/) + ->element_exists('p[class$=type-name-article]') + ->text_like('p[class$=type-name-article]' => qr/article/); + + my $delete_type = $self->url_for('delete_type', name => 'XTestType'); + $op->get_ok($delete_type)->status_is(200) + ->element_exists('p[class$=type-name-article]') + ->element_exists_not('p[class$=type-name-XTestType]'); + + $op->get_ok($all_page)->status_is(200)->status_isnt(404) + ->element_exists_not('p[class$=type-name-XTestType]') + ->element_exists('p[class$=type-name-article]') + ->text_like('p[class$=type-name-article]' => qr/article/); +}; + +ok(1); +done_testing(); + diff --git a/t/900-frontend/007_teams.t b/t/900-frontend/007_teams.t new file mode 100644 index 0000000..9a686e4 --- /dev/null +++ b/t/900-frontend/007_teams.t @@ -0,0 +1,49 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use BibSpace; +use BibSpace::Functions::Core; + +my $op = Test::Mojo->new('BibSpace'); +my $self = $op->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +$op->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +$self = $op->app; +my $app_config = $op->app->config; +$op->ua->max_redirects(10); + +my @tts = $op->app->repo->tagTypes_all; +my $some_tt = $tts[0]; + +subtest 'Show all teams' => sub { + my $page = $self->url_for('all_teams'); + $op->get_ok($page)->status_is(200)->status_isnt(404)->status_isnt(500) + ->element_exists('span[class$=team-name-SE-WUERZBURG]') + ->text_like('span[class$=team-name-SE-WUERZBURG]' => qr/SE-WUERZBURG/); +}; + +subtest 'Edit team' => sub { + my $page = $self->url_for('edit_team', id => 1); + $op->get_ok($page)->status_is(200)->status_isnt(404)->status_isnt(500) + ->element_exists('h1[class$=team-name-SE-WUERZBURG]') + ->text_like('h1[class$=team-name-SE-WUERZBURG]' => qr/Team SE-WUERZBURG/); +}; + +subtest 'Edit non-existing team' => sub { + my $page = $self->url_for('edit_team', id => 999); + $op->get_ok($page)->status_is(200)->status_isnt(404)->status_isnt(500) + ->element_exists('div[class$=alert-danger]') + ->text_like('div[class$=alert-danger]' => qr/There is no team with id 999/); +}; + +# TODO: Finish this test suite + +ok(1); +done_testing(); + diff --git a/t/900-frontend/007_user_management_anyone.t b/t/900-frontend/007_user_management_anyone.t index 91a281c..fcf667c 100644 --- a/t/900-frontend/007_user_management_anyone.t +++ b/t/900-frontend/007_user_management_anyone.t @@ -63,7 +63,7 @@ subtest 'Setting new password' => sub { ); my $user = $t_anyone->app->repo->users_find(sub { $_->login eq 'pub_admin' }); - my $token2 = $user->forgot_token; + my $token2 = $user->get_forgot_pass_token; ok($token2, "Checking token exists"); is(length($token2), 32, "Checking token length"); @@ -97,7 +97,7 @@ subtest 'Setting new password' => sub { note "============ Registration anyone ============"; -$t_anyone->get_ok("/noregister")->status_is(200) +$t_anyone->get_ok($self->url_for('registration_disabled'))->status_is(200) ->content_like(qr/Registration is disabled/i); subtest 'User management: public registration' => sub { diff --git a/t/900-frontend/008_user_management_logged.t b/t/900-frontend/008_user_management_logged.t index 5487173..6580ee6 100644 --- a/t/900-frontend/008_user_management_logged.t +++ b/t/900-frontend/008_user_management_logged.t @@ -29,7 +29,7 @@ ok(defined $token); is(length($token), 32); subtest 'User management: noregister' => sub { - $t_logged_in->get_ok("/noregister")->status_is(200) + $t_logged_in->get_ok($self->url_for('registration_disabled'))->status_is(200) ->content_like(qr/Registration is disabled/i); }; diff --git a/t/900-frontend/009_cron_and_rest.t b/t/900-frontend/009_cron_and_rest.t index 0cc0fb1..835bb57 100644 --- a/t/900-frontend/009_cron_and_rest.t +++ b/t/900-frontend/009_cron_and_rest.t @@ -10,49 +10,39 @@ use BibSpace::Controller::Cron; my $t_anyone = Test::Mojo->new('BibSpace'); my $self = $t_anyone->app; - $t_anyone->ua->inactivity_timeout(3600); - use BibSpace::TestManager; TestManager->apply_fixture($t_anyone->app); -$t_anyone->get_ok("/cron/night")->status_isnt(404, "Checking: 404 /cron/night") - ->status_isnt(500, "Checking: 500 /cron/night"); - -note "============ Testing cron day ============"; -BibSpace::Controller::Cron::do_cron_day($self); -note "============ Testing cron night ============"; -BibSpace::Controller::Cron::do_cron_night($self); -note "============ Testing cron week ============"; -BibSpace::Controller::Cron::do_cron_week($self); -note "============ Testing cron month ============"; -BibSpace::Controller::Cron::do_cron_month($self); - -note "============ Testing statistics output ============"; -ok($self->app->statistics->toLines); - -note "============ Leftovers... ============"; +subtest "Run cron functions" => sub { + is(BibSpace::Controller::Cron::do_cron_day($self), + 1, "Cron day should return 1"); + is(BibSpace::Controller::Cron::do_cron_night($self), + 1, "Cron night should return 1"); + is(BibSpace::Controller::Cron::do_cron_week($self), + 1, "Cron week should return 1"); + is(BibSpace::Controller::Cron::do_cron_month($self), + 1, "Cron month should return 1"); +}; + +subtest "Test cron pages" => sub { + $t_anyone->get_ok("/cron")->status_isnt(404)->status_isnt(500) + ->content_like(qr/Cron day/)->content_like(qr/Cron night/) + ->content_like(qr/Cron week/)->content_like(qr/Cron month/) + ->content_like(qr/last run/); + + $t_anyone->get_ok("/cron/night")->status_isnt(404)->status_isnt(500) + ->content_like(qr/Cron level 1/); + + $t_anyone->get_ok("/cron/week")->status_isnt(404)->status_isnt(500) + ->content_like(qr/Cron level 2/); + + $t_anyone->get_ok("/cron/month")->status_isnt(404)->status_isnt(500) + ->content_like(qr/Cron level 3/); +}; + +subtest "Test statistics function" => sub { + ok($self->app->statistics->toLines); +}; -use BibSpace::Util::DummyUidProvider; - -my $duip - = DummyUidProvider->new(for_type => 'Dummy', logger => $self->app->logger); -ok($duip); -ok($duip->reset); -ok($duip->registerUID); -ok($duip->generateUID); - -use BibSpace::Util::SmartUidProvider; - -my $suip = SmartUidProvider->new( - idProviderClassName => 'IntegerUidProvider', - logger => $self->app->logger -); -ok($suip->_init('Entry')); -ok($suip->generateUID('Entry')); -ok($suip->generateUID('Entry')); -ok($suip->registerUID('Entry', 999999)); - -ok(1); done_testing(); - diff --git a/t/900-frontend/009_preferences.t b/t/900-frontend/009_preferences.t new file mode 100644 index 0000000..3317fc2 --- /dev/null +++ b/t/900-frontend/009_preferences.t @@ -0,0 +1,32 @@ +use Mojo::Base -strict; +use Test::More; +use Test::Mojo; +use Mojo::IOLoop; + +use BibSpace; +use BibSpace::Functions::Core; +use BibSpace::Controller::Cron; + +my $op = Test::Mojo->new('BibSpace'); +my $self = $op->app; +use BibSpace::TestManager; +TestManager->apply_fixture($self->app); + +$op->post_ok( + '/do_login' => {Accept => '*/*'}, + form => {user => 'pub_admin', pass => 'asdf'} +); + +$self = $op->app; +my $app_config = $op->app->config; +$op->ua->max_redirects(10); + +subtest 'Persistence Status Ajax' => sub { + my $page = $self->url_for('persistence_status_ajax'); + + $op->get_ok($page)->status_isnt(404)->status_isnt(500) + ->text_like('pre' => qr/CNT_mysql/)->text_unlike('pre' => qr/CNT_smart/) + ->text_like('pre' => qr/entity/); +}; + +done_testing(); diff --git a/t/99_rest.t b/t/99_rest.t index f60f1b8..8a44217 100644 --- a/t/99_rest.t +++ b/t/99_rest.t @@ -2,14 +2,14 @@ use Mojo::Base -strict; use Test::More; use Test::Mojo; -`rm test-backups/*sql`; +`rm -f test-backups/*sql`; my $anyone = Test::Mojo->new('BibSpace'); if ($anyone->app->mode ne 'production') { - `rm backups/*dat`; - `rm backups/*sql`; - `rm test-backups/*dat`; - `rm test-backups/*sql`; + `rm -f backups/*dat`; + `rm -f backups/*sql`; + `rm -f test-backups/*dat`; + `rm -f test-backups/*sql`; } ok(1); diff --git a/util/ArchitectureGeneratorRepositoryTest.pl b/util/ArchitectureGeneratorRepositoryTest.pl deleted file mode 100644 index a4b0325..0000000 --- a/util/ArchitectureGeneratorRepositoryTest.pl +++ /dev/null @@ -1,179 +0,0 @@ -use strict; -use warnings; -use v5.10; -use Template; -use DateTime; -use File::Basename; -use File::Path qw/make_path/; - -################################### Example of business entity for testing -package Entry; -use Moose; -has 'bib' => (is => 'rw', isa => 'Str'); -has 'year' => (is => 'rw', isa => 'Int', default => 2017); -__PACKAGE__->meta->make_immutable; -no Moose; - -package BibSpace::Model::ILogger; -use Moose::Role; -use DateTime; - -sub log { - my $self = shift; - my $type = shift; # info, warn, error, debug - my $msg = shift; # text to log - my $origin = shift // "unknown"; # method from where the msg originates - my $time = DateTime->now(); - print "\t[$time] $type: $msg (Origin: $origin).\n"; -} - -sub debug { - my $self = shift; - my $msg = shift; - my $origin = shift // 'unknown'; - $self->log('debu', $msg, $origin); -} - -sub entering { - my $self = shift; - my $msg = shift; - my $origin = shift // 'unknown'; - $self->log('ente', $msg, $origin); -} - -sub exiting { - my $self = shift; - my $msg = shift; - my $origin = shift // 'unknown'; - $self->log('exit', $msg, $origin); -} - -sub info { - my $self = shift; - my $msg = shift; - my $origin = shift // 'unknown'; - $self->log('info', $msg, $origin); -} - -sub warn { - my $self = shift; - my $msg = shift; - my $origin = shift // 'unknown'; - $self->log('warn', $msg, $origin); -} - -sub error { - my $self = shift; - my $msg = shift; - my $origin = shift // 'unknown'; - $self->log('erro', $msg, $origin); -} - -no Moose; - -package BibSpace::Model::SimpleLogger; -use Moose; -with 'BibSpace::Model::ILogger'; - -sub debug { - my $self = shift; - my $msg = shift; - my $time = DateTime->now(); - print "[$time] DEBU: $msg\n"; -} - -__PACKAGE__->meta->make_immutable; -no Moose; - -package main; - -## lets see if this generates properly -use Try::Tiny; - -try { - # use BibSpace::Model::DAO::DAOFactory; - - my $dbHandle = "DB stuff"; # DBI->connect(...) - my $redisHandle = "Redis stuff"; # Redis->connect(...) - - my $logger = BibSpace::Model::SimpleLogger->new(); - - my %backendConfig = ( - backends => [ - { - prio => 1, - type => 'BibSpace::Model::DAO::ArrayDAOFactory', - handle => "NOHANDLE" - }, - { - prio => 2, - type => 'BibSpace::Model::DAO::RedisDAOFactory', - handle => $redisHandle - }, - { - prio => 2, - type => 'BibSpace::Model::DAO::MySQLDAOFactory', - handle => $dbHandle - }, - ] - ); - - # use Data::Dumper; - # print "MAIN: using config: ".Dumper \%backendConfig; - - # require BibSpace::Model::Repository::RepositoryFactory; - # my $factory - # = BibSpace::Model::Repository::RepositoryFactory->new(logger => $logger) - # ->getInstance( - # 'BibSpace::Model::Repository::LayeredRepositoryFactory', - # \%backendConfig - # ); - -# say "######## CALLING 4 times \$factory->getEntriesRepository();"; -# my $erepo = $factory->getEntriesRepository(); -# $factory->getEntriesRepository(); -# $factory->getEntriesRepository(); -# $factory->getEntriesRepository(); -# # there should be only one logger entry on the screen with: DEBU: Initializing filed instanceEntriesRepo in class BibSpace::Model::Repository::LayeredRepositoryFactory -# say "######## CALLING \$erepo->all();"; - - # my @allEntries = $erepo->all(); - - # my $entry = Entry->new( bib => "sth", year => 2012 ); - # my $entry2 = Entry->new( bib => "sth", year => 2011 ); - # my @entries = ( $entry, $entry2 ); - # $erepo->save(@entries); - - # my @someEntries = $erepo->filter( sub { $_->year == 2011 } ); #fake! - -} -catch { - say "Caught exception: $_"; -}; - -# Test for DAO only - -# try{ -# require BibSpace::Model::DAO::DAOFactory; -# BibSpace::Model::DAO::DAOFactory->import; -# my $dbHandle = "DB stuff"; # DBI->connect(...) -# my $redisHandle = "Redis stuff"; # Redis->connect(...) - -# my $logger = BibSpace::Model::SimpleLogger->new(); -# my $daoFactory; -# $daoFactory = BibSpace::Model::DAO::DAOFactory->new(logger=>$logger)->getInstance( 'BibSpace::Model::DAO::MySQLDAOFactory', $dbHandle ); -# # $daoFactory = BibSpace::Model::DAO::DAOFactory->new()->getInstance( 'Redis', $redisHandle ); - -# my $dao = $daoFactory->getEntryDao(); -# # my @entries = $dao->all(); - -# my $entry = Entry->new( bib => "sth", year =>2012 ); -# my $entry2 = Entry->new( bib => "sth", year => 2011 ); -# my @entries = ($entry, $entry2); -# $dao->save(@entries); -# # my @someEntries = $dao->filter("blah"); #fake! -# my @someEntries2 = $dao->filter(sub{$_->year==2011}); #fake! -# } -# catch{ -# say "Caught exception: $_"; -# };