From fe59b0b108ea7996536e68d66f7a72891e69eba7 Mon Sep 17 00:00:00 2001 From: blavenie <benoit.lavenier@e-is.pro> Date: Wed, 30 Nov 2016 18:16:49 +0100 Subject: [PATCH] Refactoring : split ES plugin in 3 plugins: es-core, es-user and es-gchange --- doc/fr/development_tutorial.md | 2 +- .../core/client/model/bma/Constants.java | 4 +- .../client/model/elasticsearch/Event.java | 22 + .../org/duniter/core/service/MailService.java | 22 + .../duniter/core/service/MailServiceImpl.java | 22 + .../cli/action/HelpCliAction.java | 51 - .../cli/action/IndexerCliAction.java | 187 -- .../service/event/EventCodes.java | 9 - .../pom.xml | 238 +-- .../main/assembly/config/elasticsearch.yml | 2 +- .../src/main/assembly/config/logging.yml | 2 +- .../src/main/assembly/standalone.xml | 42 - .../action/AbstractRestPostIndexAction.java | 4 +- .../action/AbstractRestPostUpdateAction.java | 4 +- .../elasticsearch/action/RestModule.java | 12 +- .../history/RestHistoryDeleteIndexAction.java | 6 +- .../market/RestMarketCategoryAction.java | 2 +- .../market/RestMarketCommentIndexAction.java | 4 +- .../market/RestMarketCommentUpdateAction.java | 4 +- .../market/RestMarketRecordIndexAction.java | 4 +- .../market/RestMarketRecordUpdateAction.java | 4 +- .../message/RestMessageInboxIndexAction.java | 4 +- .../message/RestMessageOutboxIndexAction.java | 4 +- .../registry/RestRegistryCategoryAction.java | 2 +- .../RestRegistryCommentIndexAction.java | 4 +- .../RestRegistryRecordIndexAction.java | 4 +- .../RestRegistryRecordUpdateAction.java | 4 +- .../RestregistryCommentUpdateAction.java | 5 +- .../user/RestUserProfileIndexAction.java | 4 +- .../user/RestUserProfileUpdateAction.java | 4 +- .../user/RestUserSettingsIndexAction.java | 4 +- .../user/RestUserSettingsUpdateAction.java | 4 +- .../src/test/es-home/config/elasticsearch.yml | 5 +- .../src/test/es-home/config/logging.yml | 2 +- .../LICENSE | 0 duniter4j-es-core/pom.xml | 175 ++ .../src/license/THIRD-PARTY.properties | 0 .../src/main/assembly/plugin.xml | 0 .../main/filtered-resources/duniter4j.config | 0 .../main/filtered-resources/log4j.properties | 0 .../plugin-descriptor.properties | 4 +- .../org/duniter/elasticsearch/Plugin.java | 11 +- .../org/duniter/elasticsearch/PluginInit.java | 132 ++ .../duniter/elasticsearch/PluginSettings.java | 12 +- .../exception/AccessDeniedException.java | 0 .../DuniterElasticsearchException.java | 0 .../exception/DuplicateIndexIdException.java | 0 .../exception/InvalidFormatException.java | 0 .../exception/InvalidSignatureException.java | 0 .../exception/NodeConfigException.java | 0 .../exception/NotFoundException.java | 0 .../duniter/elasticsearch/model/Currency.java | 0 .../elasticsearch/model/SearchResult.java | 0 .../rest/AbstractRestPostIndexAction.java | 83 + .../rest/AbstractRestPostUpdateAction.java | 82 + .../elasticsearch/rest/RestModule.java | 47 + .../rest/RestXContentBuilder.java | 0 .../rest/XContentRestResponse.java | 0 .../rest/XContentThrowableRestResponse.java | 3 +- .../currency/RestCurrencyIndexAction.java | 4 +- .../security/RestSecurityAuthAction.java | 2 +- .../security/RestSecurityController.java | 2 +- .../rest}/security/RestSecurityFilter.java | 2 +- .../RestSecurityGetChallengeAction.java | 2 +- .../security/SecurityModule.java | 0 .../challenge/ChallengeMessageStore.java | 0 .../security/token/SecurityTokenStore.java | 0 .../service/AbstractService.java | 25 +- .../service/AbstractSynchroService.java | 87 +- .../service/BlockchainService.java | 12 +- .../service/CurrencyService.java | 280 +-- .../elasticsearch/service/ServiceLocator.java | 4 +- .../elasticsearch/service/ServiceModule.java | 31 +- .../service/changes}/ChangeEvent.java | 12 +- .../service/changes/ChangeListener.java | 6 + .../service/changes/ChangeService.java | 70 +- .../service/changes}/ChangeSource.java | 2 +- .../service/changes/ChangeUtils.java | 65 + .../elasticsearch/threadpool/ThreadPool.java | 16 +- .../duniter/elasticsearch/util/Desktop.java | 0 .../elasticsearch/util/DesktopPower.java | 0 .../util/os/win/WindowsPower.java | 0 .../util/os/win/handle/CWPSSTRUCT.java | 0 .../util/os/win/handle/HANDLER_ROUTINE.java | 0 .../util/os/win/handle/WNDPROC.java | 0 .../util/os/win/libs/Kernel32Ex.java | 0 .../os/win/wrap/GetLastErrorException.java | 0 .../util/os/win/wrap/WNDCLASSEXWrap.java | 0 .../websocket/WebsocketChangeEndPoint.java | 14 +- .../websocket/WebsocketModule.java | 8 +- .../websocket/WebsocketServer.java | 86 + .../javax.websocket.ContainerProvider | 0 .../services/org.duniter.core.beans.Bean | 0 ...rg.nuiton.config.ApplicationConfigProvider | 0 .../i18n/duniter4j-es-core_en_GB.properties | 4 - .../i18n/duniter4j-es-core_fr_FR.properties | 6 +- .../market-categories-bulk-insert.json | 0 .../src/main/resources/plugin-security.policy | 2 +- .../registry-categories-bulk-insert.json | 0 .../src/test/es-home/config/elasticsearch.yml | 178 ++ .../src/test/es-home/config/logging.yml | 97 ++ .../duniter/elasticsearch/TestFixtures.java | 0 .../duniter/elasticsearch/TestResource.java | 0 .../service/BlockchainServiceTest.java | 0 .../services/org.duniter.core.beans.Bean | 0 .../src/test/resources/curl_test.sh | 0 ...4j-elasticsearch-localhost-node.properties | 0 .../duniter4j-elasticsearch-test.properties | 0 .../src/test/resources/log4j.properties | 0 .../test/resources/registry-test-records.json | 0 duniter4j-es-gchange/pom.xml | 129 ++ .../src/license/THIRD-PARTY.properties | 26 + .../src/main/assembly/plugin.xml | 44 + .../main/filtered-resources/log4j.properties | 32 + .../plugin-descriptor.properties | 9 + .../duniter/elasticsearch/gchange/Plugin.java | 84 + .../elasticsearch/gchange/PluginInit.java | 134 ++ .../elasticsearch/gchange/PluginSettings.java | 67 + .../gchange/rest/RestModule.java | 48 + .../rest/market/RestMarketCategoryAction.java | 38 + .../market/RestMarketCommentIndexAction.java | 43 + .../market/RestMarketCommentUpdateAction.java | 43 + .../market/RestMarketRecordIndexAction.java | 43 + .../market/RestMarketRecordUpdateAction.java | 43 + .../registry/RestRegistryCategoryAction.java | 38 + .../RestRegistryCommentIndexAction.java | 43 + .../RestRegistryRecordIndexAction.java | 43 + .../RestRegistryRecordUpdateAction.java | 43 + .../RestregistryCommentUpdateAction.java | 43 + .../service}/CitiesRegistryService.java | 8 +- .../gchange}/service/MarketService.java | 5 +- .../gchange/service/RegistryService.java | 357 ++++ .../gchange/service/ServiceModule.java | 36 + .../gchange/service/SynchroService.java | 74 + .../src/main/misc/cities-fr.geoJson.txt | 0 .../src/main/misc/index.sh | 0 .../registry-categories-naf2008_liste_n5.ods | Bin .../market-categories-bulk-insert.json | 151 ++ .../src/main/resources/plugin-security.policy | 5 + .../registry-categories-bulk-insert.json | 1506 +++++++++++++++++ .../src/test/es-home/config/elasticsearch.yml | 178 ++ .../src/test/es-home/config/logging.yml | 97 ++ .../duniter/elasticsearch/TestFixtures.java | 28 + .../duniter/elasticsearch/TestResource.java | 141 ++ .../service/BlockchainServiceTest.java | 172 ++ .../RegistryRecordIndexerServiceTest.java | 1 + .../services/org.duniter.core.beans.Bean | 14 + .../src/test/resources/curl_test.sh | 16 + ...4j-elasticsearch-localhost-node.properties | 12 + .../duniter4j-elasticsearch-test.properties | 16 + .../src/test/resources/log4j.properties | 18 + .../test/resources/registry-test-records.json | 23 + duniter4j-es-user/pom.xml | 106 ++ .../src/main/assembly/plugin.xml | 43 + .../main/filtered-resources/log4j.properties | 32 + .../plugin-descriptor.properties | 9 + .../duniter/elasticsearch/user/Plugin.java | 86 + .../elasticsearch/user/PluginInit.java | 75 +- .../elasticsearch/user/PluginSettings.java | 104 ++ .../elasticsearch/user/rest/RestModule.java | 52 + .../history/RestHistoryDeleteIndexAction.java | 45 + .../message/RestMessageInboxIndexAction.java | 44 + .../message/RestMessageOutboxIndexAction.java | 44 + .../rest/user/RestUserProfileIndexAction.java | 44 + .../user/RestUserProfileUpdateAction.java | 45 + .../user/RestUserSettingsIndexAction.java | 45 + .../user/RestUserSettingsUpdateAction.java | 45 + .../user}/service/HistoryService.java | 3 +- .../user}/service/MessageService.java | 8 +- .../user/service/ServiceModule.java | 41 + .../user/service/SynchroService.java | 75 + .../user}/service/UserService.java | 6 +- .../user/service/event/UserEvent.java | 30 +- .../user/service/event/UserEventCodes.java | 34 + .../user/service/event/UserEventListener.java | 6 + .../user/service/event/UserEventService.java | 114 +- .../websocket/WebsocketUserEventEndPoint.java | 98 ++ .../src/main/misc/cities-fr.geoJson.txt | 8 + duniter4j-es-user/src/main/misc/curl_test.sh | 16 + duniter4j-es-user/src/main/misc/index.sh | 14 + .../registry-categories-naf2008_liste_n5.ods | Bin 0 -> 70946 bytes .../i18n/duniter4j-es-user_en_GB.properties | 4 + .../i18n/duniter4j-es-user_fr_FR.properties | 4 + .../src/main/resources/plugin-security.policy | 5 + pom.xml | 7 +- 185 files changed, 6330 insertions(+), 1076 deletions(-) delete mode 100644 duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/HelpCliAction.java delete mode 100644 duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/IndexerCliAction.java delete mode 100644 duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventCodes.java rename {duniter4j-elasticsearch => duniter4j-es-assembly}/pom.xml (68%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/assembly/config/elasticsearch.yml (99%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/assembly/config/logging.yml (98%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/assembly/standalone.xml (63%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java (97%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java (97%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/RestModule.java (87%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java (88%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java (94%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java (94%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java (88%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java (91%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/test/es-home/config/elasticsearch.yml (98%) rename {duniter4j-elasticsearch => duniter4j-es-assembly}/src/test/es-home/config/logging.yml (98%) rename {duniter4j-elasticsearch => duniter4j-es-core}/LICENSE (100%) create mode 100644 duniter4j-es-core/pom.xml rename {duniter4j-elasticsearch => duniter4j-es-core}/src/license/THIRD-PARTY.properties (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/assembly/plugin.xml (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/filtered-resources/duniter4j.config (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/filtered-resources/log4j.properties (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/filtered-resources/plugin-descriptor.properties (66%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/Plugin.java (91%) create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/PluginSettings.java (97%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/NodeConfigException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/model/Currency.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/model/SearchResult.java (100%) create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostIndexAction.java create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostUpdateAction.java create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java (95%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest}/currency/RestCurrencyIndexAction.java (94%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest}/security/RestSecurityAuthAction.java (98%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest}/security/RestSecurityController.java (98%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest}/security/RestSecurityFilter.java (97%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest}/security/RestSecurityGetChallengeAction.java (97%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/security/SecurityModule.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/security/challenge/ChallengeMessageStore.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/service/AbstractService.java (99%) rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/synchro/SynchroService.java => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java (78%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java (99%) rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java (58%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java (97%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java (70%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes}/ChangeEvent.java (88%) create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeRegister.java => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java (75%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes}/ChangeSource.java (97%) create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java (94%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/Desktop.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/DesktopPower.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/WindowsPower.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/handle/CWPSSTRUCT.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/handle/HANDLER_ROUTINE.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/handle/WNDPROC.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/libs/Kernel32Ex.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/GetLastErrorException.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/WNDCLASSEXWrap.java (100%) rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServerEndPoint.java => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketChangeEndPoint.java (84%) rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangesModule.java => duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketModule.java (87%) create mode 100644 duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketServer.java rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/resources/META-INF/services/javax.websocket.ContainerProvider (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/resources/META-INF/services/org.duniter.core.beans.Bean (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/resources/META-INF/services/org.nuiton.config.ApplicationConfigProvider (100%) rename duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_en_GB.properties => duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties (92%) rename duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_fr_FR.properties => duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties (91%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/resources/market-categories-bulk-insert.json (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/resources/plugin-security.policy (73%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/main/resources/registry-categories-bulk-insert.json (100%) create mode 100644 duniter4j-es-core/src/test/es-home/config/elasticsearch.yml create mode 100644 duniter4j-es-core/src/test/es-home/config/logging.yml rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/java/org/duniter/elasticsearch/TestFixtures.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/java/org/duniter/elasticsearch/TestResource.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/resources/META-INF/services/org.duniter.core.beans.Bean (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/resources/curl_test.sh (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/resources/duniter4j-elasticsearch-localhost-node.properties (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/resources/duniter4j-elasticsearch-test.properties (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/resources/log4j.properties (100%) rename {duniter4j-elasticsearch => duniter4j-es-core}/src/test/resources/registry-test-records.json (100%) create mode 100644 duniter4j-es-gchange/pom.xml create mode 100644 duniter4j-es-gchange/src/license/THIRD-PARTY.properties create mode 100644 duniter4j-es-gchange/src/main/assembly/plugin.xml create mode 100644 duniter4j-es-gchange/src/main/filtered-resources/log4j.properties create mode 100644 duniter4j-es-gchange/src/main/filtered-resources/plugin-descriptor.properties create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginInit.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginSettings.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/RestModule.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestregistryCommentUpdateAction.java rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/registry => duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service}/CitiesRegistryService.java (99%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch => duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange}/service/MarketService.java (98%) create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java create mode 100644 duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java rename {duniter4j-elasticsearch => duniter4j-es-gchange}/src/main/misc/cities-fr.geoJson.txt (100%) rename {duniter4j-elasticsearch => duniter4j-es-gchange}/src/main/misc/index.sh (100%) rename {duniter4j-elasticsearch => duniter4j-es-gchange}/src/main/misc/registry-categories-naf2008_liste_n5.ods (100%) create mode 100644 duniter4j-es-gchange/src/main/resources/market-categories-bulk-insert.json create mode 100644 duniter4j-es-gchange/src/main/resources/plugin-security.policy create mode 100644 duniter4j-es-gchange/src/main/resources/registry-categories-bulk-insert.json create mode 100644 duniter4j-es-gchange/src/test/es-home/config/elasticsearch.yml create mode 100644 duniter4j-es-gchange/src/test/es-home/config/logging.yml create mode 100644 duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestFixtures.java create mode 100644 duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestResource.java create mode 100644 duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java rename {duniter4j-elasticsearch => duniter4j-es-gchange}/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java (96%) create mode 100644 duniter4j-es-gchange/src/test/resources/META-INF/services/org.duniter.core.beans.Bean create mode 100755 duniter4j-es-gchange/src/test/resources/curl_test.sh create mode 100644 duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-localhost-node.properties create mode 100644 duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-test.properties create mode 100644 duniter4j-es-gchange/src/test/resources/log4j.properties create mode 100644 duniter4j-es-gchange/src/test/resources/registry-test-records.json create mode 100644 duniter4j-es-user/pom.xml create mode 100644 duniter4j-es-user/src/main/assembly/plugin.xml create mode 100644 duniter4j-es-user/src/main/filtered-resources/log4j.properties create mode 100644 duniter4j-es-user/src/main/filtered-resources/plugin-descriptor.properties create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java => duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java (57%) create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/RestModule.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/history/RestHistoryDeleteIndexAction.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageInboxIndexAction.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageOutboxIndexAction.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileIndexAction.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileUpdateAction.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsIndexAction.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsUpdateAction.java rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch => duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user}/service/HistoryService.java (98%) rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch => duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user}/service/MessageService.java (96%) create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java rename {duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch => duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user}/service/UserService.java (98%) rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/Event.java => duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEvent.java (58%) create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventCodes.java create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventListener.java rename duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventService.java => duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventService.java (78%) create mode 100644 duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java create mode 100644 duniter4j-es-user/src/main/misc/cities-fr.geoJson.txt create mode 100755 duniter4j-es-user/src/main/misc/curl_test.sh create mode 100755 duniter4j-es-user/src/main/misc/index.sh create mode 100644 duniter4j-es-user/src/main/misc/registry-categories-naf2008_liste_n5.ods create mode 100644 duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties create mode 100644 duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties create mode 100644 duniter4j-es-user/src/main/resources/plugin-security.policy diff --git a/doc/fr/development_tutorial.md b/doc/fr/development_tutorial.md index 27bcfc6a..2cccc229 100644 --- a/doc/fr/development_tutorial.md +++ b/doc/fr/development_tutorial.md @@ -286,7 +286,7 @@ Ouvrir votre IDE, et ouvrir le projet Duniter4j. Dans le répertoire `duniter4j-elasticsearch/src/main/java`, cherchez et répérez dans le code : -- les controlleurs REST : package `org.duniter.elasticsearch.action` +- les controlleurs REST : package `org.duniter.elasticsearch.rest` - les services d'indexation : package `org.duniter.elasticsearch.service`. * Il existe un service d'indexation par type de stockage. Par exemple : `BlockchainService`, `UserService`, etc. diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java index ff2ee8c2..1df3da70 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java @@ -28,6 +28,8 @@ package org.duniter.core.client.model.bma; public interface Constants { interface Regex { - String CURRENCY_NAME = "[a-zA-Z_-]"; + String USER_ID = "[A-Za-z0-9_-]+"; + String CURRENCY_NAME = "[A-Za-z0-9_-]"; + String PUBKEY = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}"; } } diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Event.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Event.java index d535ad58..ffb926ef 100644 --- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Event.java +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Event.java @@ -1,5 +1,27 @@ package org.duniter.core.client.model.elasticsearch; +/* + * #%L + * Duniter4j :: Core Client API + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + import org.nuiton.i18n.I18n; import java.util.Locale; diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailService.java b/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailService.java index 3bb08591..64975b25 100644 --- a/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailService.java +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailService.java @@ -1,5 +1,27 @@ package org.duniter.core.service; +/* + * #%L + * Duniter4j :: Core Shared + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + import org.duniter.core.beans.Bean; import org.duniter.core.exception.TechnicalException; diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailServiceImpl.java b/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailServiceImpl.java index d2175242..ec7bb898 100644 --- a/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailServiceImpl.java +++ b/duniter4j-core-shared/src/main/java/org/duniter/core/service/MailServiceImpl.java @@ -1,5 +1,27 @@ package org.duniter.core.service; +/* + * #%L + * Duniter4j :: Core Shared + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + import org.duniter.core.exception.TechnicalException; import org.duniter.core.util.StringUtils; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/HelpCliAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/HelpCliAction.java deleted file mode 100644 index 5fd18e0b..00000000 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/HelpCliAction.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.duniter.elasticsearch.cli.action; - -/* - * #%L - * SIH-Adagio :: Shared - * $Id:$ - * $HeadURL:$ - * %% - * Copyright (C) 2012 - 2014 Ifremer - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - -public class HelpCliAction { - - public void show() { - StringBuilder sb = new StringBuilder(); - - sb.append("Usage: duniter4j-elaticsearch.<sh|bat> <commands> [options]\n\n") - .append("Commands:\n\n") - .append(" start Start elastic search node\n") - .append(" index Index blocks from BMA Node\n") - .append(" reset-data Reset all indexed data (blocks and records)\n") - .append(" reset-data-blocks Reset only indexed blocks (from uCoin node's)\n") - .append(" reset-data-records Reset only indexed records\n") - .append("\n") - .append("\n") - .append("Options:\n\n") - .append(" --help Output usage information\n") - .append(" -h --host <user> uCoin node host (with Basic Merkled API)\n") - .append(" -p --port <pwd> uCoin node port (with Basic Merkled API)\n") - .append("\n") - .append(" -esh --es-host <user> ElasticSearch node host\n") - .append(" -esp --es-port <pwd> ElasticSearch node port\n"); - - System.out.println(sb.toString()); - } -} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/IndexerCliAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/IndexerCliAction.java deleted file mode 100644 index 5220383c..00000000 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/cli/action/IndexerCliAction.java +++ /dev/null @@ -1,187 +0,0 @@ -package org.duniter.elasticsearch.cli.action; - -/* - * #%L - * SIH-Adagio :: Shared - * $Id:$ - * $HeadURL:$ - * %% - * Copyright (C) 2012 - 2014 Ifremer - * %% - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public - * License along with this program. If not, see - * <http://www.gnu.org/licenses/gpl-3.0.html>. - * #L% - */ - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class IndexerCliAction { - /* Logger */ - private static final Logger log = LoggerFactory.getLogger(IndexerCliAction.class); - - /* - public void indexBlocksFromNode() { - - final boolean async = ServiceLocator.instance().getElasticSearchService().isNodeInstance(); - - Runnable runnable = new Runnable() { - @Override - public void run() { - PluginSettings config = PluginSettings.instance(); - final Peer peer = checkConfigAndGetPeer(config); - final BlockchainService blockIndexerService = ServiceLocator.instance().getBlockIndexerService(); - - // Will create the blockchain if not exist - blockIndexerService.indexLastBlocks(peer); - - if (async) { - ServiceLocator.instance().getBlockchainRemoteService().addNewBlockListener(peer, new WebsocketClientEndpoint.MessageHandler() { - @Override - public void handleMessage(String message) { - String currencyName = GsonUtils.getValueFromJSONAsString(message, "blockchain"); - *///blockIndexerService.indexLastBlockFromJson(peer, message, true /*refresh*/, true /*wait*/); - //blockIndexerService.indexCurrentBlockAsJson(currencyName, message, true /*wait*/); - /* } - }); - } - } - }; - - // Async execution - if (async) { - ServiceLocator.instance().getExecutorService().execute(runnable); - } - - // Synchrone execution - else { - runnable.run(); - } - } - - public void resetAllData() { - resetAllCurrencies(); - resetDataBlocks(); - resetMarketRecords(); - resetRegistry(); - } - - public void resetAllCurrencies() { - CurrencyRegistryService currencyIndexerService = ServiceLocator.instance().getRegistryCurrencyIndexerService(); - currencyIndexerService.deleteAllCurrencies(); - } - - public void resetDataBlocks() { - BlockchainRemoteService blockchainService = ServiceLocator.instance().getBlockchainRemoteService(); - BlockchainService indexerService = ServiceLocator.instance().getBlockIndexerService(); - PluginSettings config = PluginSettings.instance(); - Peer peer = checkConfigAndGetPeer(config); - - try { - // Get the blockchain name from node - BlockchainParameters parameter = blockchainService.getParameters(peer); - if (parameter == null) { - log.error(String.format("Could not connect to node [%s:%s]", - config.getNodeBmaHost(), config.getNodeBmaPort())); - return; - } - String currencyName = parameter.getCurrency(); - - log.info(String.format("Reset data for index [%s]", currencyName)); - - // Delete then create index on blockchain - boolean indexExists = indexerService.existsIndex(currencyName); - if (indexExists) { - indexerService.deleteIndex(currencyName); - indexerService.createIndex(currencyName); - } - - - log.info(String.format("Successfully reset data for index [%s]", currencyName)); - } catch(Exception e) { - log.error("Error during reset data: " + e.getMessage(), e); - } - } - - public void resetMarketRecords() { - RecordMarketService recordIndexerService = ServiceLocator.instance().getMarketRecordIndexerService(); - CategoryMarketService categoryIndexerService = ServiceLocator.instance().getMarketCategoryIndexerService(); - - try { - // Delete then create index on records - boolean indexExists = recordIndexerService.existsIndex(); - if (indexExists) { - recordIndexerService.deleteIndex(); - } - log.info(String.format("Successfully reset market records")); - - categoryIndexerService.createIndex(); - categoryIndexerService.initCategories(); - log.info(String.format("Successfully re-initialized market categories data")); - - } catch(Exception e) { - log.error("Error during reset market records: " + e.getMessage(), e); - } - } - - public void resetRegistry() { - RecordRegistryService recordIndexerService = ServiceLocator.instance().getRegistryRecordIndexerService(); - CategoryRegistryService categoryIndexerService = ServiceLocator.instance().getRegistryCategoryIndexerService(); - CitiesRegistryService citiesIndexerService = ServiceLocator.instance().getRegistryCitiesIndexerService(); - - try { - // Delete then create index on records - if (recordIndexerService.existsIndex()) { - recordIndexerService.deleteIndex(); - } - recordIndexerService.createIndex(); - log.info(String.format("Successfully reset registry records")); - - - if (categoryIndexerService.existsIndex()) { - categoryIndexerService.deleteIndex(); - } - categoryIndexerService.createIndex(); - categoryIndexerService.initCategories(); - log.info(String.format("Successfully re-initialized registry categories")); - - if (citiesIndexerService.existsIndex()) { - citiesIndexerService.deleteIndex(); - } - citiesIndexerService.initCities(); - log.info(String.format("Successfully re-initialized registry cities")); - - } catch(Exception e) { - log.error("Error during reset registry records: " + e.getMessage(), e); - } - }*/ - - /* -- internal methods -- */ - - /*protected Peer checkConfigAndGetPeer(PluginSettings config) { - if (StringUtils.isBlank(config.getNodeBmaHost())) { - log.error("ERROR: node host is required"); - System.exit(-1); - return null; - } - if (config.getNodeBmaPort() <= 0) { - log.error("ERROR: node port is required"); - System.exit(-1); - return null; - } - - Peer peer = new Peer(config.getNodeBmaHost(), config.getNodeBmaPort()); - return peer; - }*/ -} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventCodes.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventCodes.java deleted file mode 100644 index f790e046..00000000 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventCodes.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.duniter.elasticsearch.service.event; - -/** - * Created by blavenie on 29/11/16. - */ -public enum EventCodes { - - NODE_STARTED -} diff --git a/duniter4j-elasticsearch/pom.xml b/duniter4j-es-assembly/pom.xml similarity index 68% rename from duniter4j-elasticsearch/pom.xml rename to duniter4j-es-assembly/pom.xml index b4d5c8d8..b3acfffe 100644 --- a/duniter4j-elasticsearch/pom.xml +++ b/duniter4j-es-assembly/pom.xml @@ -8,163 +8,31 @@ </parent> <groupId>org.duniter</groupId> - <artifactId>duniter4j-elasticsearch</artifactId> - <packaging>jar</packaging> - <name>Duniter4j :: ElasticSearch Plugin</name> + <artifactId>duniter4j-es-assembly</artifactId> + <packaging>pom</packaging> + <name>Duniter4j :: ElasticSearch Assembly</name> <properties> <!-- bundle configuration --> - <bundlePrefix>duniter4j-elasticsearch-${project.version}</bundlePrefix> + <bundlePrefix>duniter4j-es-${project.version}</bundlePrefix> <!-- i18n configuration --> - <i18n.bundleOutputName>duniter4j-elasticsearch-i18n</i18n.bundleOutputName> + <i18n.bundleOutputName>duniter4j-es-i18n</i18n.bundleOutputName> <i18n.generateCsvFile>true</i18n.generateCsvFile> <i18n.bundleCsvFile> ${maven.gen.dir}/resources/META-INF/${i18n.bundleOutputName}.csv </i18n.bundleCsvFile> <config.i18nBundleName>${i18n.bundleOutputName}</config.i18nBundleName> - - <duniter4j-elasticsearch.config>${project.basedir}/src/test/resources/duniter4j-elasticsearch-test.properties</duniter4j-elasticsearch.config> - <assembly.skip>false</assembly.skip> </properties> - <dependencies> - <dependency> - <groupId>org.duniter</groupId> - <artifactId>duniter4j-core-client</artifactId> - <version>${project.version}</version> - <exclusions> - <exclusion> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </exclusion> - </exclusions> - </dependency> - <!-- LOGGING DEPENDENCIES - SLF4J --> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - </dependency> - <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-log4j12</artifactId> - <optional>true</optional> - </dependency> - <dependency> - <groupId>log4j</groupId> - <artifactId>log4j</artifactId> - <optional>true</optional> - <scope>runtime</scope> - </dependency> - - <!-- Elastic Search --> - <dependency> - <groupId>org.elasticsearch</groupId> - <artifactId>elasticsearch</artifactId> - <scope>provided</scope> - </dependency> - <dependency> - <groupId>com.fasterxml.jackson.core</groupId> - <artifactId>jackson-databind</artifactId> - </dependency> - - <!-- JNA (need for OS shutdown hook) --> - <dependency> - <groupId>net.java.dev.jna</groupId> - <artifactId>jna</artifactId> - <scope>compile</scope> - </dependency> - <dependency> - <groupId>net.java.dev.jna</groupId> - <artifactId>jna-platform</artifactId> - <exclusions> - <exclusion> - <groupId>net.java.dev.jna</groupId> - <artifactId>jna</artifactId> - </exclusion> - </exclusions> - </dependency> - -<!-- - <dependency> - <groupId>com.github.spullara.mustache.java</groupId> - <artifactId>compiler</artifactId> - <version>0.8.13</version> - <scope>compile</scope> - </dependency> ---> - - <dependency> - <groupId>org.glassfish.tyrus</groupId> - <artifactId>tyrus-server</artifactId> - </dependency> - <dependency> - <groupId>org.glassfish.tyrus</groupId> - <artifactId>tyrus-container-grizzly-server</artifactId> - </dependency> - - <!-- Unit test --> - <dependency> - <groupId>junit</groupId> - <artifactId>junit</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - <build> - <resources> - <resource> - <directory>src/main/filtered-resources</directory> - <filtering>true</filtering> - <includes> - <include>*.config</include> - <include>**/*.properties</include> - </includes> - </resource> - <resource> - <directory>src/main/resources</directory> - <filtering>false</filtering> - </resource> - </resources> <plugins> - <plugin> - <groupId>org.nuiton.i18n</groupId> - <artifactId>i18n-maven-plugin</artifactId> - - <executions> - <execution> - <id>scan-sources</id> - <configuration> - <entries> - <entry> - <specificGoal>parserValidation</specificGoal> - <basedir>${maven.src.dir}/main/java/</basedir> - <includes> - <param>**/**-validation.xml</param> - </includes> - </entry> - </entries> - </configuration> - <goals> - <goal>parserJava</goal> - <goal>parserValidation</goal> - <goal>gen</goal> - </goals> - </execution> - <execution> - <id>make-bundle</id> - <goals> - <goal>bundle</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> <artifactId>maven-dependency-plugin</artifactId> <executions> + <!-- unpack ES --> <execution> <id>unpack-elasticsearch</id> <goals> @@ -185,6 +53,8 @@ <skip>${assembly.skip}</skip> </configuration> </execution> + + <!-- unpack attachment plugin --> <execution> <id>unpack-mapper-attachments-plugin</id> <goals> @@ -205,30 +75,78 @@ <skip>${assembly.skip}</skip> </configuration> </execution> - </executions> - </plugin> - <plugin> - <artifactId>maven-assembly-plugin</artifactId> - <executions> + <!-- unpack ES core plugin --> <execution> - <id>assembly-plugin</id> - <phase>package</phase> + <id>unpack-es-core-plugin</id> <goals> - <goal>single</goal> + <goal>unpack</goal> </goals> + <phase>prepare-package</phase> <configuration> - <attach>true</attach> - <appendAssemblyId>false</appendAssemblyId> - <finalName>${bundlePrefix}</finalName> - <descriptors> - <descriptor> - ${basedir}/src/main/assembly/plugin.xml - </descriptor> - </descriptors> - <skipAssembly>${assembly.skip}</skipAssembly> + <artifactItems> + <artifactItem> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-core</artifactId> + <version>${project.version}</version> + <type>zip</type> + </artifactItem> + </artifactItems> + <outputDirectory>${project.build.directory}/elasticsearch-${elasticsearch.version}/plugins/duniter4j-es-core</outputDirectory> + <silent>true</silent> + <skip>${assembly.skip}</skip> + </configuration> + </execution> + + <!-- unpack ES user plugin --> + <execution> + <id>unpack-es-user-plugin</id> + <goals> + <goal>unpack</goal> + </goals> + <phase>prepare-package</phase> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-user</artifactId> + <version>${project.version}</version> + <type>zip</type> + </artifactItem> + </artifactItems> + <outputDirectory>${project.build.directory}/elasticsearch-${elasticsearch.version}/plugins/duniter4j-es-user</outputDirectory> + <silent>true</silent> + <skip>${assembly.skip}</skip> + </configuration> + </execution> + + <!-- unpack ES gchange plugin --> + <execution> + <id>unpack-es-gchange-plugin</id> + <goals> + <goal>unpack</goal> + </goals> + <phase>prepare-package</phase> + <configuration> + <artifactItems> + <artifactItem> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-gchange</artifactId> + <version>${project.version}</version> + <type>zip</type> + </artifactItem> + </artifactItems> + <outputDirectory>${project.build.directory}/elasticsearch-${elasticsearch.version}/plugins/duniter4j-es-gchange</outputDirectory> + <silent>true</silent> + <skip>${assembly.skip}</skip> </configuration> </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> <execution> <id>assembly-standalone</id> <phase>package</phase> @@ -291,10 +209,14 @@ <!-- reuse standalone files --> <then> <delete failonerror="false"> - <fileset dir="${run.es.home}/plugins/${project.artifactId}" includes="${project.artifactId}-*.jar" /> + <fileset dir="${run.es.home}/plugins/${project.groupId}" includes="duniter4j-*.jar" /> </delete> - <copy todir="${run.es.home}/plugins/${project.artifactId}" overwrite="true"> - <fileset dir="${project.build.directory}" includes="*.jar"> + <copy todir="${run.es.home}/plugins/${project.groupId}" overwrite="true"> + <fileset dir="../duniter4j-es-core/target" includes="duniter4j-*${project.version}.jar"> + </fileset> + <fileset dir="../duniter4j-es-user/target" includes="duniter4j-*${project.version}.jar"> + </fileset> + <fileset dir="../duniter4j-es-gchange/target" includes="duniter4j-*${project.version}.jar"> </fileset> </copy> </then> diff --git a/duniter4j-elasticsearch/src/main/assembly/config/elasticsearch.yml b/duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml similarity index 99% rename from duniter4j-elasticsearch/src/main/assembly/config/elasticsearch.yml rename to duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml index bfde540b..b7eec87e 100644 --- a/duniter4j-elasticsearch/src/main/assembly/config/elasticsearch.yml +++ b/duniter4j-es-assembly/src/main/assembly/config/elasticsearch.yml @@ -100,7 +100,7 @@ http.cors.enabled: true # # Require explicit names when deleting indices: # -# action.destructive_requires_name: true +# rest.destructive_requires_name: true security.manager.enabled: false diff --git a/duniter4j-elasticsearch/src/main/assembly/config/logging.yml b/duniter4j-es-assembly/src/main/assembly/config/logging.yml similarity index 98% rename from duniter4j-elasticsearch/src/main/assembly/config/logging.yml rename to duniter4j-es-assembly/src/main/assembly/config/logging.yml index 2fe5b777..82d2ed08 100644 --- a/duniter4j-elasticsearch/src/main/assembly/config/logging.yml +++ b/duniter4j-es-assembly/src/main/assembly/config/logging.yml @@ -2,7 +2,7 @@ es.logger.level: INFO rootLogger: ${es.logger.level}, console, file logger: - # log action execution errors for easier debugging + # log rest execution errors for easier debugging action: DEBUG # deprecation logging, turn to DEBUG to see them diff --git a/duniter4j-elasticsearch/src/main/assembly/standalone.xml b/duniter4j-es-assembly/src/main/assembly/standalone.xml similarity index 63% rename from duniter4j-elasticsearch/src/main/assembly/standalone.xml rename to duniter4j-es-assembly/src/main/assembly/standalone.xml index 7ad07ec1..e9d6a6ee 100644 --- a/duniter4j-elasticsearch/src/main/assembly/standalone.xml +++ b/duniter4j-es-assembly/src/main/assembly/standalone.xml @@ -62,51 +62,9 @@ <include>logging.yml</include> </includes> </fileSet> - - <fileSet> - <directory>target</directory> - <outputDirectory>plugins/${project.artifactId}</outputDirectory> - <includes> - <include>${project.build.finalName}.${project.packaging}</include> - </includes> - </fileSet> - - <fileSet> - <directory>target/classes</directory> - <outputDirectory>plugins/${project.artifactId}</outputDirectory> - <includes> - <include>plugin-descriptor.properties</include> - <include>plugin-security.policy</include> - </includes> - </fileSet> - - <fileSet> - <outputDirectory>plugins/${project.artifactId}</outputDirectory> - <includes> - <include>LICENSE</include> - </includes> - </fileSet> </fileSets> <dependencySets> - <dependencySet> - <outputDirectory>plugins/${project.artifactId}</outputDirectory> - <useProjectArtifact>true</useProjectArtifact> - <useTransitiveFiltering>true</useTransitiveFiltering> - <excludes> - <exclude>org.elasticsearch:elasticsearch</exclude> - <exclude>net.java.dev.jna:jna</exclude> - <exclude>com.fasterxml.jackson.core:jackson-core</exclude> - <exclude>log4j:log4j</exclude> - <exclude>com.google.guava:guava</exclude> - <!-- lib to include in elasticsearch/libs/ --> - <exclude>javax.websocket:javax.websocket-api</exclude> - <exclude>org.glassfish.tyrus:tyrus-client</exclude> - <exclude>org.glassfish.tyrus:tyrus-container-grizzly-client</exclude> - </excludes> - <fileMode>0555</fileMode> - </dependencySet> - <dependencySet> <outputDirectory>lib</outputDirectory> <useProjectArtifact>true</useProjectArtifact> diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java index 9b2eb4d6..5f715ec3 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java @@ -23,9 +23,9 @@ package org.duniter.elasticsearch.action; */ import org.duniter.core.exception.BusinessException; -import org.duniter.elasticsearch.action.security.RestSecurityController; -import org.duniter.elasticsearch.exception.DuniterElasticsearchException; import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; import org.elasticsearch.client.Client; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLoggerFactory; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java index ee02158a..c56a0a23 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java @@ -23,9 +23,9 @@ package org.duniter.elasticsearch.action; */ import org.duniter.core.exception.BusinessException; -import org.duniter.elasticsearch.action.security.RestSecurityController; -import org.duniter.elasticsearch.exception.DuniterElasticsearchException; import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; import org.elasticsearch.client.Client; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.ESLoggerFactory; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/RestModule.java similarity index 87% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/RestModule.java index 6c68949b..a67d6f2a 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/RestModule.java @@ -22,16 +22,16 @@ package org.duniter.elasticsearch.action; * #L% */ -import org.duniter.elasticsearch.action.currency.RestCurrencyIndexAction; -import org.duniter.elasticsearch.action.history.RestHistoryDeleteIndexAction; +import org.duniter.elasticsearch.rest.currency.RestCurrencyIndexAction; +import org.duniter.elasticsearch.rest.history.RestHistoryDeleteIndexAction; import org.duniter.elasticsearch.action.market.*; import org.duniter.elasticsearch.action.message.RestMessageInboxIndexAction; import org.duniter.elasticsearch.action.message.RestMessageOutboxIndexAction; import org.duniter.elasticsearch.action.registry.*; -import org.duniter.elasticsearch.action.security.RestSecurityAuthAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; -import org.duniter.elasticsearch.action.security.RestSecurityFilter; -import org.duniter.elasticsearch.action.security.RestSecurityGetChallengeAction; +import org.duniter.elasticsearch.rest.security.RestSecurityAuthAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.rest.security.RestSecurityFilter; +import org.duniter.elasticsearch.rest.security.RestSecurityGetChallengeAction; import org.duniter.elasticsearch.action.user.RestUserProfileIndexAction; import org.duniter.elasticsearch.action.user.RestUserProfileUpdateAction; import org.duniter.elasticsearch.action.user.RestUserSettingsIndexAction; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java similarity index 88% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java index 79936a4e..0778b8d6 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.history; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.HistoryService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; @@ -34,7 +34,7 @@ import org.elasticsearch.rest.RestController; public class RestHistoryDeleteIndexAction extends AbstractRestPostIndexAction { - private static final ESLogger log = ESLoggerFactory.getLogger(RestHistoryDeleteIndexAction.class.getName()); + private static final ESLogger log = ESLoggerFactory.getLogger(org.duniter.elasticsearch.rest.history.RestHistoryDeleteIndexAction.class.getName()); @Inject public RestHistoryDeleteIndexAction(Settings settings, RestController controller, Client client, diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java similarity index 94% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java index 6ece1150..7bf4f6ae 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCategoryAction.java @@ -22,7 +22,7 @@ package org.duniter.elasticsearch.action.market; * #L% */ -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MarketService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.rest.RestRequest; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java index 08452ec8..37b278a0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.market; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MarketService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java index b1840100..a11d15ab 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.market; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MarketService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java index 2d2b8d56..1f7cfc68 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.market; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MarketService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java index 9b1b99da..7b1cd758 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.market; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MarketService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java index d6c5bdfd..1ab8b7d5 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/message/RestMessageInboxIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.message; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MessageService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java index 70e08b0a..5fc0c8ba 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/message/RestMessageOutboxIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.message; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.MessageService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java similarity index 94% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java index 7bfdf238..db635061 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCategoryAction.java @@ -22,7 +22,7 @@ package org.duniter.elasticsearch.action.registry; * #L% */ -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.RegistryService; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.rest.RestRequest; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java index 1c112ce4..b1fb5969 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryCommentIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.registry; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.RegistryService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java index 1d3c5768..98d4fce0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.registry; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.RegistryService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java index dcfba5eb..3ea0b8a0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordUpdateAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.registry; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.RegistryService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java similarity index 88% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java index 6137eacb..329a5ae7 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/registry/RestregistryCommentUpdateAction.java @@ -22,9 +22,8 @@ package org.duniter.elasticsearch.action.registry; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; -import org.duniter.elasticsearch.service.MarketService; +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.RegistryService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java index 9e738f86..535fb070 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.user; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.UserService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java index e6d60417..c287a82e 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.user; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.UserService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java index cb9f7c75..1948f583 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.user; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.UserService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java rename to duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java index 3cf36bae..b7b3bd71 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java +++ b/duniter4j-es-assembly/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java @@ -22,8 +22,8 @@ package org.duniter.elasticsearch.action.user; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; -import org.duniter.elasticsearch.action.security.RestSecurityController; +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; import org.duniter.elasticsearch.service.UserService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml b/duniter4j-es-assembly/src/test/es-home/config/elasticsearch.yml similarity index 98% rename from duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml rename to duniter4j-es-assembly/src/test/es-home/config/elasticsearch.yml index fc10df32..828ddbd0 100644 --- a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml +++ b/duniter4j-es-assembly/src/test/es-home/config/elasticsearch.yml @@ -100,7 +100,7 @@ http.cors.enabled: true # # Require explicit names when deleting indices: # -# action.destructive_requires_name: true +# rest.destructive_requires_name: true security.manager.enabled: false @@ -173,3 +173,6 @@ duniter.mail.admin: blavenie@EIS-DEV # Mail subject prefix # #duniter.mail.subject.prefix: [Duniter4j ES] + +duniter.changes.listenSource: */block +duniter.ws.port: 9400 diff --git a/duniter4j-elasticsearch/src/test/es-home/config/logging.yml b/duniter4j-es-assembly/src/test/es-home/config/logging.yml similarity index 98% rename from duniter4j-elasticsearch/src/test/es-home/config/logging.yml rename to duniter4j-es-assembly/src/test/es-home/config/logging.yml index 63c95362..15cfa3e1 100644 --- a/duniter4j-elasticsearch/src/test/es-home/config/logging.yml +++ b/duniter4j-es-assembly/src/test/es-home/config/logging.yml @@ -2,7 +2,7 @@ es.logger.level: INFO rootLogger: ${es.logger.level}, console, file logger: - # log action execution errors for easier debugging + # log rest execution errors for easier debugging action: DEBUG # deprecation logging, turn to DEBUG to see them diff --git a/duniter4j-elasticsearch/LICENSE b/duniter4j-es-core/LICENSE similarity index 100% rename from duniter4j-elasticsearch/LICENSE rename to duniter4j-es-core/LICENSE diff --git a/duniter4j-es-core/pom.xml b/duniter4j-es-core/pom.xml new file mode 100644 index 00000000..06cf5452 --- /dev/null +++ b/duniter4j-es-core/pom.xml @@ -0,0 +1,175 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.duniter</groupId> + <artifactId>duniter4j</artifactId> + <version>0.3.5-SNAPSHOT</version> + </parent> + + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-core</artifactId> + <packaging>jar</packaging> + <name>Duniter4j :: ElasticSearch Core plugin</name> + + <properties> + <!-- i18n configuration --> + <i18n.bundleOutputName>duniter4j-es-core-i18n</i18n.bundleOutputName> + <i18n.generateCsvFile>true</i18n.generateCsvFile> + <i18n.bundleCsvFile> + ${maven.gen.dir}/resources/META-INF/${i18n.bundleOutputName}.csv + </i18n.bundleCsvFile> + <config.i18nBundleName>${i18n.bundleOutputName}</config.i18nBundleName> + + </properties> + + <dependencies> + <dependency> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-core-client</artifactId> + <version>${project.version}</version> + <exclusions> + <exclusion> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </exclusion> + </exclusions> + </dependency> + <!-- LOGGING DEPENDENCIES - SLF4J --> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-log4j12</artifactId> + <optional>true</optional> + </dependency> + <dependency> + <groupId>log4j</groupId> + <artifactId>log4j</artifactId> + <optional>true</optional> + <scope>runtime</scope> + </dependency> + + <!-- Elastic Search --> + <dependency> + <groupId>org.elasticsearch</groupId> + <artifactId>elasticsearch</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + </dependency> + + <!-- JNA (need for OS shutdown hook) --> + <dependency> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna</artifactId> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna-platform</artifactId> + <exclusions> + <exclusion> + <groupId>net.java.dev.jna</groupId> + <artifactId>jna</artifactId> + </exclusion> + </exclusions> + </dependency> + <dependency> + <groupId>org.glassfish.tyrus</groupId> + <artifactId>tyrus-server</artifactId> + </dependency> + <dependency> + <groupId>org.glassfish.tyrus</groupId> + <artifactId>tyrus-container-grizzly-server</artifactId> + </dependency> + + <!-- Unit test --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/filtered-resources</directory> + <filtering>true</filtering> + <includes> + <include>*.config</include> + <include>**/*.properties</include> + </includes> + </resource> + <resource> + <directory>src/main/resources</directory> + <filtering>false</filtering> + </resource> + </resources> + + <plugins> + <plugin> + <groupId>org.nuiton.i18n</groupId> + <artifactId>i18n-maven-plugin</artifactId> + + <executions> + <execution> + <id>scan-sources</id> + <configuration> + <entries> + <entry> + <specificGoal>parserValidation</specificGoal> + <basedir>${maven.src.dir}/main/java/</basedir> + <includes> + <param>**/**-validation.xml</param> + </includes> + </entry> + </entries> + </configuration> + <goals> + <goal>parserJava</goal> + <goal>parserValidation</goal> + <goal>gen</goal> + </goals> + </execution> + <execution> + <id>make-bundle</id> + <goals> + <goal>bundle</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <id>assembly-plugin</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <attach>true</attach> + <appendAssemblyId>false</appendAssemblyId> + <finalName>${project.artifactId}-${project.version}</finalName> + <descriptors> + <descriptor> + ${basedir}/src/main/assembly/plugin.xml + </descriptor> + </descriptors> + <skipAssembly>${assembly.skip}</skipAssembly> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> diff --git a/duniter4j-elasticsearch/src/license/THIRD-PARTY.properties b/duniter4j-es-core/src/license/THIRD-PARTY.properties similarity index 100% rename from duniter4j-elasticsearch/src/license/THIRD-PARTY.properties rename to duniter4j-es-core/src/license/THIRD-PARTY.properties diff --git a/duniter4j-elasticsearch/src/main/assembly/plugin.xml b/duniter4j-es-core/src/main/assembly/plugin.xml similarity index 100% rename from duniter4j-elasticsearch/src/main/assembly/plugin.xml rename to duniter4j-es-core/src/main/assembly/plugin.xml diff --git a/duniter4j-elasticsearch/src/main/filtered-resources/duniter4j.config b/duniter4j-es-core/src/main/filtered-resources/duniter4j.config similarity index 100% rename from duniter4j-elasticsearch/src/main/filtered-resources/duniter4j.config rename to duniter4j-es-core/src/main/filtered-resources/duniter4j.config diff --git a/duniter4j-elasticsearch/src/main/filtered-resources/log4j.properties b/duniter4j-es-core/src/main/filtered-resources/log4j.properties similarity index 100% rename from duniter4j-elasticsearch/src/main/filtered-resources/log4j.properties rename to duniter4j-es-core/src/main/filtered-resources/log4j.properties diff --git a/duniter4j-elasticsearch/src/main/filtered-resources/plugin-descriptor.properties b/duniter4j-es-core/src/main/filtered-resources/plugin-descriptor.properties similarity index 66% rename from duniter4j-elasticsearch/src/main/filtered-resources/plugin-descriptor.properties rename to duniter4j-es-core/src/main/filtered-resources/plugin-descriptor.properties index 1ab4c6e9..27e01fac 100644 --- a/duniter4j-elasticsearch/src/main/filtered-resources/plugin-descriptor.properties +++ b/duniter4j-es-core/src/main/filtered-resources/plugin-descriptor.properties @@ -1,5 +1,5 @@ -name=duniter4j-elasticsearch -description=Plugin for Duniter node indexation +name=duniter4j-es-core +description=Plugin for Duniter version=${project.version} site=false jvm=true diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/Plugin.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java similarity index 91% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/Plugin.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java index bb8715b4..a64334b6 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/Plugin.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java @@ -23,11 +23,11 @@ package org.duniter.elasticsearch; */ import com.google.common.collect.Lists; -import org.duniter.elasticsearch.action.RestModule; -import org.duniter.elasticsearch.node.DuniterNode; +import org.duniter.elasticsearch.rest.RestModule; import org.duniter.elasticsearch.security.SecurityModule; import org.duniter.elasticsearch.service.ServiceModule; import org.duniter.elasticsearch.threadpool.ThreadPool; +import org.duniter.elasticsearch.websocket.WebsocketModule; import org.elasticsearch.common.component.LifecycleComponent; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Module; @@ -65,10 +65,11 @@ public class Plugin extends org.elasticsearch.plugins.Plugin { return modules; } modules.add(new SecurityModule()); + + modules.add(new WebsocketModule()); modules.add(new RestModule()); + modules.add(new ServiceModule()); - // TODO : must be tested inside full release assembly - //modules.add(new ChangesModule()); return modules; } @@ -81,7 +82,7 @@ public class Plugin extends org.elasticsearch.plugins.Plugin { } components.add(PluginSettings.class); components.add(ThreadPool.class); - components.add(DuniterNode.class); + components.add(PluginInit.class); return components; } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java new file mode 100644 index 00000000..110b3a0d --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java @@ -0,0 +1,132 @@ +package org.duniter.elasticsearch; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.core.client.model.elasticsearch.Currency; +import org.duniter.core.client.model.local.Peer; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.service.BlockchainService; +import org.duniter.elasticsearch.service.CurrencyService; +import org.duniter.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.inject.Injector; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestRequest; + +/** + * Created by blavenie on 17/06/16. + */ +public class PluginInit extends AbstractLifecycleComponent<PluginInit> { + + private final PluginSettings pluginSettings; + private final ThreadPool threadPool; + private final Injector injector; + private final static ESLogger logger = Loggers.getLogger("duniter.core"); + private final Client client; + + @Inject + public PluginInit(Client client, Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) { + super(settings); + this.pluginSettings = pluginSettings; + this.threadPool = threadPool; + this.injector = injector; + this.client = client; + } + + @Override + protected void doStart() { + threadPool.scheduleOnClusterHealthStatus(() -> { + createIndices(); + + // Waiting cluster back to GREEN or YELLOW state, before synchronize + threadPool.scheduleOnClusterHealthStatus(() -> { + synchronize(); + }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN); + }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN); + } + + @Override + protected void doStop() { + + } + + @Override + protected void doClose() { + + } + + protected void createIndices() { + + boolean reloadIndices = pluginSettings.reloadIndices(); + + if (reloadIndices) { + if (logger.isInfoEnabled()) { + logger.info("Reloading all Duniter core indices..."); + } + + injector.getInstance(CurrencyService.class) + .deleteIndex() + .createIndexIfNotExists(); + + if (logger.isInfoEnabled()) { + logger.info("Reloading all Duniter indices... [OK]"); + } + } + else { + if (logger.isInfoEnabled()) { + logger.info("Checking Duniter core indices..."); + } + + injector.getInstance(CurrencyService.class).createIndexIfNotExists(); + + if (logger.isInfoEnabled()) { + logger.info("Checking Duniter core indices... [OK]"); + } + } + } + + protected void synchronize() { + if (pluginSettings.enableBlockchainSync()) { + + Peer peer = pluginSettings.checkAndGetPeer(); + + // Index (or refresh) node's currency + Currency currency = injector.getInstance(CurrencyService.class).indexCurrencyFromPeer(peer, true); + + // Add access to currency index + injector.getInstance(RestSecurityController.class).allowIndexType(RestRequest.Method.GET, + currency.getCurrencyName(), + BlockchainService.BLOCK_TYPE); + + // Index blocks (and listen if new block appear) + injector.getInstance(BlockchainService.class) + .indexLastBlocks(peer) + .listenAndIndexNewBlock(peer); + } + } +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/PluginSettings.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/PluginSettings.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java index 265473a9..5fe14728 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/PluginSettings.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java @@ -60,7 +60,7 @@ import static org.nuiton.i18n.I18n.t; */ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { - private Settings settings; + protected final Settings settings; /** * Delegate application config. @@ -269,6 +269,14 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { return settings.get("duniter.mail.subject.prefix", "[Duniter4j ES]"); } + public int getWebSocketPort() { + return settings.getAsInt("duniter.ws.port", 9200); + } + + public String getWebSocketHost() { + return settings.get("network.host", "locahost"); + } + /* protected methods */ protected void initI18n() throws IOException { @@ -301,6 +309,6 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> { } protected String getI18nBundleName() { - return "duniter4j-elasticsearch-i18n"; + return "duniter4j-es-core-i18n"; } } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NodeConfigException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/NodeConfigException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NodeConfigException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/NodeConfigException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/model/Currency.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/Currency.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/model/Currency.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/Currency.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/model/SearchResult.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResult.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/model/SearchResult.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/model/SearchResult.java diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostIndexAction.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostIndexAction.java new file mode 100644 index 00000000..c2a0f7fd --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostIndexAction.java @@ -0,0 +1,83 @@ +package org.duniter.elasticsearch.rest; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.core.exception.BusinessException; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.*; + +import static org.elasticsearch.rest.RestRequest.Method.GET; +import static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestStatus.OK; + +public abstract class AbstractRestPostIndexAction extends BaseRestHandler { + + private static ESLogger log = null; + + private final JsonIndexer indexer; + + + public AbstractRestPostIndexAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + String indexName, + String typeName, + JsonIndexer indexer) { + super(settings, controller, client); + controller.registerHandler(POST, + String.format("/%s/%s", indexName, typeName), + this); + securityController.allowIndexType(POST, indexName, typeName); + securityController.allowIndexType(GET, indexName, typeName); + log = ESLoggerFactory.getLogger(String.format("[%s]", indexName)); + this.indexer = indexer; + } + + @Override + protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception { + + try { + String id = indexer.handleJson(request.content().toUtf8()); + restChannel.sendResponse(new BytesRestResponse(OK, id)); + } + catch(DuniterElasticsearchException | BusinessException e) { + log.error(e.getMessage(), e); + restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); + } + catch(Exception e) { + log.error(e.getMessage(), e); + } + } + + + public interface JsonIndexer { + String handleJson(String json) throws DuniterElasticsearchException, BusinessException; + } + + + +} \ No newline at end of file diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostUpdateAction.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostUpdateAction.java new file mode 100644 index 00000000..ede1eb5e --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/AbstractRestPostUpdateAction.java @@ -0,0 +1,82 @@ +package org.duniter.elasticsearch.rest; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.core.exception.BusinessException; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.*; + +import static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestStatus.OK; + +public abstract class AbstractRestPostUpdateAction extends BaseRestHandler { + + private static ESLogger log = null; + + private final JsonUpdater updater; + + + public AbstractRestPostUpdateAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + String indexName, + String typeName, + JsonUpdater updater) { + super(settings, controller, client); + controller.registerHandler(POST, + String.format("/%s/%s/{id}/_update", indexName, typeName), + this); + securityController.allowIndexType(POST, indexName, typeName); + log = ESLoggerFactory.getLogger(String.format("[%s]", indexName)); + this.updater = updater; + } + + @Override + protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception { + String id = request.param("id"); + + try { + updater.handleJson(request.content().toUtf8(), id); + restChannel.sendResponse(new BytesRestResponse(OK, id)); + } + catch(DuniterElasticsearchException | BusinessException e) { + log.error(e.getMessage(), e); + restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); + } + catch(Exception e) { + log.error(e.getMessage(), e); + } + } + + + public interface JsonUpdater { + void handleJson(String json, String id) throws DuniterElasticsearchException, BusinessException; + } + + + +} \ No newline at end of file diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java new file mode 100644 index 00000000..68e8542c --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java @@ -0,0 +1,47 @@ +package org.duniter.elasticsearch.rest; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.currency.RestCurrencyIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityAuthAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.rest.security.RestSecurityFilter; +import org.duniter.elasticsearch.rest.security.RestSecurityGetChallengeAction; +import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.Module; + +public class RestModule extends AbstractModule implements Module { + + @Override protected void configure() { + + // Currency + bind(RestCurrencyIndexAction.class).asEagerSingleton(); + + // Authentication & Security + bind(RestSecurityGetChallengeAction.class).asEagerSingleton(); + bind(RestSecurityAuthAction.class).asEagerSingleton(); + bind(RestSecurityFilter.class).asEagerSingleton(); + bind(RestSecurityController.class).asEagerSingleton(); + + } +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java similarity index 95% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java index e78ac86d..4f498f40 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java @@ -31,7 +31,6 @@ import org.elasticsearch.rest.RestStatus; import java.io.IOException; import static org.elasticsearch.ExceptionsHelper.detailedMessage; -import static org.duniter.elasticsearch.rest.RestXContentBuilder.restContentBuilder; public class XContentThrowableRestResponse extends XContentRestResponse { @@ -44,7 +43,7 @@ public class XContentThrowableRestResponse extends XContentRestResponse { } private static XContentBuilder convert(RestRequest request, RestStatus status, Throwable t) throws IOException { - XContentBuilder builder = restContentBuilder(request).startObject() + XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).startObject() .field("error", detailedMessage(t)) .field("status", status.getStatus()); if (t != null && request.paramAsBoolean("error_trace", false)) { diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/currency/RestCurrencyIndexAction.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java similarity index 94% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/currency/RestCurrencyIndexAction.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java index dea0ce5a..100df9ec 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/currency/RestCurrencyIndexAction.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.action.currency; +package org.duniter.elasticsearch.rest.currency; /* * #%L @@ -30,7 +30,7 @@ import org.elasticsearch.rest.*; import static org.elasticsearch.rest.RestStatus.OK; /** - * A action to post a request to process a new currency/peer. + * A rest to post a request to process a new currency/peer. * * TODO : * - add security, to allow only request from admin (check signature against settings keyring) diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityAuthAction.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityAuthAction.java similarity index 98% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityAuthAction.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityAuthAction.java index 945e8dce..b62408e8 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityAuthAction.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityAuthAction.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.action.security; +package org.duniter.elasticsearch.rest.security; /* * #%L diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityController.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java similarity index 98% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityController.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java index 4c5dc3c4..b52fb2a5 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityController.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityController.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.action.security; +package org.duniter.elasticsearch.rest.security; /* * #%L diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityFilter.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityFilter.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java index 189a999b..9b6557d0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityFilter.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.action.security; +package org.duniter.elasticsearch.rest.security; /* * #%L diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityGetChallengeAction.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityGetChallengeAction.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityGetChallengeAction.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityGetChallengeAction.java index 416b75ff..9b27b92a 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/security/RestSecurityGetChallengeAction.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityGetChallengeAction.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.action.security; +package org.duniter.elasticsearch.rest.security; /* * #%L diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/SecurityModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/security/SecurityModule.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/SecurityModule.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/security/SecurityModule.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/challenge/ChallengeMessageStore.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/security/challenge/ChallengeMessageStore.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/challenge/ChallengeMessageStore.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/security/challenge/ChallengeMessageStore.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java similarity index 99% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java index f7cc9882..89bc70ad 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java @@ -30,7 +30,6 @@ import com.google.common.collect.ImmutableSet; import com.google.gson.JsonSyntaxException; import org.duniter.core.beans.Bean; import org.duniter.core.client.model.elasticsearch.Record; -import org.duniter.core.client.service.exception.HttpBadRequestException; import org.duniter.core.exception.TechnicalException; import org.duniter.core.service.CryptoService; import org.duniter.core.util.StringUtils; @@ -77,16 +76,6 @@ public abstract class AbstractService implements Bean { protected final int retryCount; protected final int retryWaitDuration; - public AbstractService(String loggerName, Client client, PluginSettings pluginSettings, CryptoService cryptoService) { - this.logger = Loggers.getLogger(loggerName); - this.client = client; - this.pluginSettings = pluginSettings; - this.cryptoService = cryptoService; - this.objectMapper = new ObjectMapper(); - this.retryCount = pluginSettings.getNodeRetryCount(); - this.retryWaitDuration = pluginSettings.getNodeRetryWaitDuration(); - } - public AbstractService(String loggerName, Client client, PluginSettings pluginSettings) { this(loggerName, client, pluginSettings, null); } @@ -99,6 +88,16 @@ public abstract class AbstractService implements Bean { this("duniter", client, pluginSettings, cryptoService); } + public AbstractService(String loggerName, Client client, PluginSettings pluginSettings, CryptoService cryptoService) { + this.logger = Loggers.getLogger(loggerName); + this.client = client; + this.pluginSettings = pluginSettings; + this.cryptoService = cryptoService; + this.objectMapper = new ObjectMapper(); + this.retryCount = pluginSettings.getNodeRetryCount(); + this.retryWaitDuration = pluginSettings.getNodeRetryWaitDuration(); + } + /* -- protected methods -- */ protected boolean existsIndex(String indexes) { @@ -124,6 +123,7 @@ public abstract class AbstractService implements Bean { JsonNode actualObj = readAndVerifyIssuerSignature(json); String issuer = getIssuer(actualObj); + if (logger.isDebugEnabled()) { logger.debug(String.format("Indexing a %s from issuer [%s]", type, issuer.substring(0, 8))); } @@ -132,7 +132,8 @@ public abstract class AbstractService implements Bean { .setSource(json) .setRefresh(false) .execute().actionGet(); - return response.getId(); + String id = response.getId(); + return id; } protected void checkIssuerAndUpdateDocumentFromJson(String index, String type, String json, String id) { diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/synchro/SynchroService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java similarity index 78% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/synchro/SynchroService.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java index b95cb1ac..57c9561f 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/synchro/SynchroService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service.synchro; +package org.duniter.elasticsearch.service; /* * #%L @@ -35,7 +35,8 @@ import org.duniter.core.service.CryptoService; import org.duniter.core.util.StringUtils; import org.duniter.elasticsearch.PluginSettings; import org.duniter.elasticsearch.exception.InvalidFormatException; -import org.duniter.elasticsearch.service.*; +import org.duniter.elasticsearch.service.AbstractService; +import org.duniter.elasticsearch.service.ServiceLocator; import org.duniter.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.action.bulk.BulkItemResponse; import org.elasticsearch.action.bulk.BulkRequestBuilder; @@ -57,66 +58,32 @@ import java.util.Set; /** * Created by blavenie on 27/10/16. */ -public class SynchroService extends AbstractService { +public abstract class AbstractSynchroService extends AbstractService { protected HttpService httpService; @Inject - public SynchroService(Client client, PluginSettings settings, CryptoService cryptoService, - ThreadPool threadPool, final ServiceLocator serviceLocator) { + public AbstractSynchroService(Client client, PluginSettings settings, CryptoService cryptoService, + ThreadPool threadPool, final ServiceLocator serviceLocator) { super("duniter.network.p2p", client, settings,cryptoService); threadPool.scheduleOnStarted(() -> { httpService = serviceLocator.getHttpService(); }); } - public void synchronize() { - logger.info("Synchronizing data..."); + /* -- protected methods -- */ + protected Peer getPeerFromAPI(String filterApiName) { // TODO : get peers from currency - use peering BMA API, and select peers with ESA (ES API) Peer peer = new Peer(pluginSettings.getDataSyncHost(), pluginSettings.getDataSyncPort()); - - synchronize(peer); - } - - public void synchronize(Peer peer) { - - long sinceTime = 0; // ToDO: get last sync time from somewhere ? (e.g. a specific index) - - logger.info(String.format("[%s] Synchronizing data since %s...", peer.toString(), sinceTime)); - - importMarketChanges(peer, sinceTime); - importRegistryChanges(peer, sinceTime); - importUserChanges(peer, sinceTime); - importMessageChanges(peer, sinceTime); - - logger.info(String.format("[%s] Synchronizing data since %s [OK]", peer.toString(), sinceTime)); - } - - public void importMarketChanges(Peer peer, long sinceTime) { - importChanges(peer, MarketService.INDEX, MarketService.RECORD_TYPE, sinceTime); - importChanges(peer, MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE, sinceTime); - } - - public void importRegistryChanges(Peer peer, long sinceTime) { - importChanges(peer, RegistryService.INDEX, RegistryService.RECORD_TYPE, sinceTime); - importChanges(peer, RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE, sinceTime); - } - - public void importUserChanges(Peer peer, long sinceTime) { - importChanges(peer, UserService.INDEX, UserService.PROFILE_TYPE, sinceTime); - importChanges(peer, UserService.INDEX, UserService.SETTINGS_TYPE, sinceTime); - } - - public void importMessageChanges(Peer peer, long sinceTime) { - importChanges(peer, MessageService.INDEX, MessageService.RECORD_TYPE, sinceTime); + return peer; } - public void importChanges(Peer peer, String index, String type, long sinceTime) { + protected void importChanges(Peer peer, String index, String type, long sinceTime) { importChanges(peer, index, type, Record.PROPERTY_ISSUER, Record.PROPERTY_TIME, sinceTime); } - public void importChanges(Peer peer, String index, String type, String issuerFieldName, String versionFieldName, long sinceTime) { + protected void importChanges(Peer peer, String index, String type, String issuerFieldName, String versionFieldName, long sinceTime) { // Create the search query BytesStreamOutput bos; @@ -125,28 +92,28 @@ public class SynchroService extends AbstractService { XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, bos); builder.startObject() .startObject("query") - // bool.should - .startObject("bool") - .startObject("should") - // time > sinceDate - .startObject("range") - .startObject("time") - .field("gte", sinceTime) - .endObject() - .endObject() - .endObject() - // currency + // bool.should + .startObject("bool") + .startObject("should") + // time > sinceDate + .startObject("range") + .startObject("time") + .field("gte", sinceTime) + .endObject() + .endObject() + .endObject() + // currency /*.startObject("filter") .startObject("term") .field("currency", "sou") // todo, filter on configured currency only .endObject() .endObject()*/ - .endObject() + .endObject() // end: query .endObject() .field("from", 0) // todo .field("size", 100) // todo - .endObject(); + .endObject(); builder.flush(); } catch(IOException e) { @@ -237,7 +204,7 @@ public class SynchroService extends AbstractService { logger.trace(String.format("[%s] [%s/%s] update _id=%s\n%s", peer, hitIndex, hitType, id, json)); } bulkRequest.add(client.prepareIndex(hitIndex, hitType, id) - .setSource(json.getBytes())); + .setSource(json.getBytes())); } } @@ -283,8 +250,4 @@ public class SynchroService extends AbstractService { IOUtils.closeQuietly(response); } } - - /* -- protected methods -- */ - - } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java similarity index 99% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java index 0786e89e..77fee625 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java @@ -36,7 +36,6 @@ import org.duniter.core.client.model.local.Peer; import org.duniter.core.client.service.bma.BlockchainRemoteService; import org.duniter.core.client.service.bma.NetworkRemoteService; import org.duniter.core.client.service.exception.BlockNotFoundException; -import org.duniter.core.client.service.exception.HttpBadRequestException; import org.duniter.core.client.service.exception.JsonSyntaxException; import org.duniter.core.exception.TechnicalException; import org.duniter.core.model.NullProgressionModel; @@ -65,7 +64,6 @@ import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.index.query.QueryBuilders; -import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHitField; import org.elasticsearch.search.aggregations.AggregationBuilders; import org.elasticsearch.search.aggregations.metrics.max.Max; @@ -89,7 +87,7 @@ public class BlockchainService extends AbstractService { private final ProgressionModel nullProgressionModel = new NullProgressionModel(); private BlockchainRemoteService blockchainRemoteService; - private RegistryService registryService; + private CurrencyService currencyService; private ThreadPool threadPool; private JsonAttributeParser blockNumberParser = new JsonAttributeParser("number"); @@ -111,8 +109,8 @@ public class BlockchainService extends AbstractService { } @Inject - public void setRegistryService(RegistryService registryService) { - this.registryService = registryService; + public void setCurrencyService(CurrencyService currencyService) { + this.currencyService = currencyService; } public BlockchainService listenAndIndexNewBlock(Peer peer){ @@ -148,8 +146,8 @@ public class BlockchainService extends AbstractService { logger.info(I18n.t("duniter4j.blockIndexerService.indexLastBlocks.task", currencyName, peer)); // Create index blockchain if need - if (!registryService.isCurrencyExists(currencyName)) { - registryService.indexCurrencyFromPeer(peer); + if (!currencyService.isCurrencyExists(currencyName)) { + currencyService.indexCurrencyFromPeer(peer); } // Check if index exists diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java similarity index 58% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java index 8d8da251..0cd8989c 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java @@ -41,13 +41,11 @@ import org.duniter.core.service.CryptoService; import org.duniter.core.util.ObjectUtils; import org.duniter.core.util.StringUtils; import org.duniter.elasticsearch.PluginSettings; -import org.duniter.elasticsearch.action.security.RestSecurityController; import org.duniter.elasticsearch.exception.AccessDeniedException; import org.duniter.elasticsearch.exception.DuplicateIndexIdException; import org.duniter.elasticsearch.exception.InvalidSignatureException; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; -import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; @@ -60,7 +58,6 @@ import org.elasticsearch.index.query.QueryBuilders; import org.elasticsearch.search.SearchHit; import org.elasticsearch.search.SearchHitField; -import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; @@ -69,24 +66,20 @@ import java.util.Objects; /** * Created by Benoit on 30/03/2015. */ -public class RegistryService extends AbstractService { +public class CurrencyService extends AbstractService { - public static final String INDEX = "registry"; - public static final String RECORD_TYPE = "record"; - public static final String RECORD_CATEGORY_TYPE = "category"; - public static final String RECORD_COMMENT_TYPE = "comment"; - public static final String CURRENCY_TYPE = "currency"; - private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json"; + public static final String INDEX = "currency"; + public static final String CURRENCY_TYPE = "record"; private final Gson gson; private BlockchainRemoteService blockchainRemoteService; @Inject - public RegistryService(Client client, + public CurrencyService(Client client, PluginSettings settings, CryptoService cryptoService, BlockchainRemoteService blockchainRemoteService) { - super("gchange." + INDEX, client, settings, cryptoService); + super("duniter." + INDEX, client, settings, cryptoService); this.gson = GsonUtils.newBuilder().create(); this.blockchainRemoteService = blockchainRemoteService; } @@ -94,12 +87,10 @@ public class RegistryService extends AbstractService { /** * Create index need for blockchain registry, if need */ - public RegistryService createIndexIfNotExists() { + public CurrencyService createIndexIfNotExists() { try { if (!existsIndex(INDEX)) { createIndex(); - - fillRecordCategories(); } } catch(JsonProcessingException e) { @@ -112,7 +103,7 @@ public class RegistryService extends AbstractService { * Create index for registry * @throws JsonProcessingException */ - public RegistryService createIndex() throws JsonProcessingException { + public CurrencyService createIndex() throws JsonProcessingException { logger.info(String.format("Creating index [%s]", INDEX)); CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX); @@ -123,15 +114,12 @@ public class RegistryService extends AbstractService { .build(); createIndexRequestBuilder.setSettings(indexSettings); createIndexRequestBuilder.addMapping(CURRENCY_TYPE, createCurrencyType()); - createIndexRequestBuilder.addMapping(RECORD_CATEGORY_TYPE, createRecordCategoryType()); - createIndexRequestBuilder.addMapping(RECORD_TYPE, createRecordType()); - createIndexRequestBuilder.addMapping(RECORD_COMMENT_TYPE, createRecordCommentType(INDEX, RECORD_COMMENT_TYPE)); createIndexRequestBuilder.execute().actionGet(); return this; } - public RegistryService deleteIndex() { + public CurrencyService deleteIndex() { deleteIndexIfExists(INDEX); return this; } @@ -140,50 +128,11 @@ public class RegistryService extends AbstractService { return super.existsIndex(INDEX); } - public RegistryService fillRecordCategories() { - if (logger.isDebugEnabled()) { - logger.debug(String.format("[%s/%s] Fill data", INDEX, RECORD_CATEGORY_TYPE)); - } - - // Insert categories - bulkFromClasspathFile(CATEGORIES_BULK_CLASSPATH_FILE, INDEX, RECORD_CATEGORY_TYPE, - // Add order attribute - new AddSequenceAttributeHandler("order", "\\{.*\"name\".*\\}", 1)); - - return this; - } - public boolean isCurrencyExists(String currencyName) { String pubkey = getSenderPubkeyByCurrencyId(currencyName); return !StringUtils.isEmpty(pubkey); } - public String indexRecordFromJson(String json) { - return checkIssuerAndIndexDocumentFromJson(INDEX, RECORD_TYPE, json); - } - - public void updateRecordFromJson(String json, String id) { - checkIssuerAndUpdateDocumentFromJson(INDEX, RECORD_TYPE, json, id); - } - - public String indexCommentFromJson(String json) { - return checkIssuerAndIndexDocumentFromJson(INDEX, RECORD_COMMENT_TYPE, json); - } - - public void updateCommentFromJson(String json, String id) { - checkIssuerAndUpdateDocumentFromJson(INDEX, RECORD_COMMENT_TYPE, json, id); - } - - public void insertRecordFromBulkFile(File bulkFile) { - - if (logger.isDebugEnabled()) { - logger.debug("Inserting records from file"); - } - - // Insert cities - bulkFromFile(bulkFile, INDEX, RECORD_TYPE); - } - /** * Retrieve the blockchain data, from peer * @@ -369,195 +318,6 @@ public class RegistryService extends AbstractService { /* -- Internal methods -- */ - - public XContentBuilder createRecordType() { - String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer(); - - try { - XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_TYPE) - .startObject("properties") - - // title - .startObject("title") - .field("type", "string") - .field("analyzer", stringAnalyzer) - .endObject() - - // description - .startObject("description") - .field("type", "string") - .field("analyzer", stringAnalyzer) - .endObject() - - // creationTime - .startObject("creationTime") - .field("type", "integer") - .endObject() - - // time - .startObject("time") - .field("type", "integer") - .endObject() - - // issuer - .startObject("issuer") - .field("type", "string") - .field("index", "not_analyzed") - .endObject() - - // pubkey - .startObject("pubkey") - .field("type", "string") - .field("index", "not_analyzed") - .endObject() - - // address - .startObject("address") - .field("type", "string") - .field("analyzer", stringAnalyzer) - .endObject() - - // city - .startObject("city") - .field("type", "string") - .endObject() - - // geoPoint - .startObject("geoPoint") - .field("type", "geo_point") - .endObject() - - // thumbnail - .startObject("thumbnail") - .field("type", "attachment") - .startObject("fields") // src - .startObject("content") // title - .field("index", "no") - .endObject() - .startObject("title") // title - .field("type", "string") - .field("store", "no") - .endObject() - .startObject("author") // title - .field("store", "no") - .endObject() - .startObject("content_type") // title - .field("store", "yes") - .endObject() - .endObject() - .endObject() - - // pictures - .startObject("pictures") - .field("type", "nested") - .field("dynamic", "false") - .startObject("properties") - .startObject("file") // file - .field("type", "attachment") - .startObject("fields") - .startObject("content") // content - .field("index", "no") - .endObject() - .startObject("title") // title - .field("type", "string") - .field("store", "yes") - .field("analyzer", stringAnalyzer) - .endObject() - .startObject("author") // author - .field("type", "string") - .field("store", "no") - .endObject() - .startObject("content_type") // content_type - .field("store", "yes") - .endObject() - .endObject() - .endObject() - .endObject() - .endObject() - - // picturesCount - .startObject("picturesCount") - .field("type", "integer") - .endObject() - - // category - .startObject("category") - .field("type", "nested") - .field("dynamic", "false") - .startObject("properties") - .startObject("id") // id - .field("type", "string") - .field("index", "not_analyzed") - .endObject() - .startObject("parent") // parent - .field("type", "string") - .field("index", "not_analyzed") - .endObject() - .startObject("name") // name - .field("type", "string") - .field("analyzer", stringAnalyzer) - .endObject() - .endObject() - .endObject() - - // tags - .startObject("tags") - .field("type", "completion") - .field("search_analyzer", "simple") - .field("analyzer", "simple") - .field("preserve_separators", "false") - .endObject() - - .endObject() - .endObject().endObject(); - - return mapping; - } - catch(IOException ioe) { - throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_TYPE, ioe.getMessage()), ioe); - } - } - - public XContentBuilder createRecordCategoryType() { - try { - XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_CATEGORY_TYPE) - .startObject("properties") - - // name - .startObject("name") - .field("type", "string") - .field("analyzer", pluginSettings.getDefaultStringAnalyzer()) - .endObject() - - // description - /*.startObject("description") - .field("type", "string") - .endObject()*/ - - // parent - .startObject("parent") - .field("type", "string") - .field("index", "not_analyzed") - .endObject() - - // tags - /*.startObject("tags") - .field("type", "completion") - .field("search_analyzer", "simple") - .field("analyzer", "simple") - .field("preserve_separators", "false") - .endObject()*/ - - .endObject() - .endObject().endObject(); - - return mapping; - } - catch(IOException ioe) { - throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_CATEGORY_TYPE, ioe.getMessage()), ioe); - } - } - public XContentBuilder createCurrencyType() { try { XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(CURRENCY_TYPE) @@ -591,30 +351,6 @@ public class RegistryService extends AbstractService { } } - /** - * - * @param jsonCategory - * @return the product id - */ - public String indexCategoryFromJson(String jsonCategory) { - if (logger.isDebugEnabled()) { - logger.debug("Indexing a category"); - } - - // Preparing indexBlocksFromNode - IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_CATEGORY_TYPE) - .setSource(jsonCategory); - - // Execute indexBlocksFromNode - IndexResponse response = indexRequest - .setRefresh(false) - .execute().actionGet(); - - return response.getId(); - } - - - /** * Retrieve a blockchain from its name * @param currencyId diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java index d2b283fd..98d46240 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java @@ -59,10 +59,10 @@ public class ServiceLocator private static BeanFactory beanFactory = null; @Inject - public ServiceLocator(Injector injector) { + public ServiceLocator() { super(); if (logger.isDebugEnabled()) { - logger.debug("Starting Duniter4j client ServiceLocator..."); + logger.debug("Starting Duniter4j ServiceLocator..."); } setBeanFactory(getOrCreateBeanFactory()); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java similarity index 70% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java index 00890a8c..9612444e 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java @@ -34,9 +34,9 @@ import org.duniter.core.client.service.bma.WotRemoteService; import org.duniter.core.client.service.local.CurrencyService; import org.duniter.core.client.service.local.PeerService; import org.duniter.core.service.CryptoService; +import org.duniter.elasticsearch.PluginInit; import org.duniter.elasticsearch.PluginSettings; -import org.duniter.elasticsearch.service.event.EventService; -import org.duniter.elasticsearch.service.synchro.SynchroService; +import org.duniter.elasticsearch.service.changes.ChangeService; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.Module; @@ -45,19 +45,13 @@ public class ServiceModule extends AbstractModule implements Module { @Override protected void configure() { bind(ServiceLocator.class).asEagerSingleton(); - // ES common service + // common services bind(PluginSettings.class).asEagerSingleton(); - bind(EventService.class).asEagerSingleton(); + bind(PluginInit.class).asEagerSingleton(); + bind(ChangeService.class).asEagerSingleton(); - // ES indexation services - bind(RegistryService.class).asEagerSingleton(); - bind(MarketService.class).asEagerSingleton(); + // indexation services bind(BlockchainService.class).asEagerSingleton(); - bind(MessageService.class).asEagerSingleton(); - bind(HistoryService.class).asEagerSingleton(); - - // ES Synchro services - bind(SynchroService.class).asEagerSingleton(); // Duniter Client API beans bindWithLocator(BlockchainRemoteService.class); @@ -74,19 +68,6 @@ public class ServiceModule extends AbstractModule implements Module { // Duniter Shared API beans bindWithLocator(CryptoService.class); bindWithLocator(org.duniter.core.service.MailService.class); - -/* - bindWithLocator(BlockchainRemoteServiceImpl.class); - bindWithLocator(NetworkRemoteServiceImpl.class); - bindWithLocator(WotRemoteServiceImpl.class); - bindWithLocator(TransactionRemoteServiceImpl.class); - bindWithLocator(Ed25519CryptoServiceImpl.class); - bindWithLocator(PeerServiceImpl.class); - bindWithLocator(CurrencyServiceImpl.class); - bindWithLocator(HttpServiceImpl.class); - bindWithLocator(MemoryCurrencyDaoImpl.class); - bindWithLocator(MemoryPeerDaoImpl.class); - bindWithLocator(DataContext.class);*/ } /* protected methods */ diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeEvent.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java similarity index 88% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeEvent.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java index f1dde614..99d6fd7a 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeEvent.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.websocket; +package org.duniter.elasticsearch.service.changes; /* * #%L @@ -43,6 +43,7 @@ import org.joda.time.DateTime; public class ChangeEvent { private final String id; + private final String index; private final String type; private final DateTime timestamp; private final Operation operation; @@ -53,8 +54,9 @@ public class ChangeEvent { INDEX,CREATE,DELETE } - public ChangeEvent(String id, String type, DateTime timestamp, Operation operation, long version, BytesReference source) { + public ChangeEvent(String index, String type, String id, DateTime timestamp, Operation operation, long version, BytesReference source) { this.id = id; + this.index = index; this.type = type; this.timestamp = timestamp; this.operation = operation; @@ -74,6 +76,10 @@ public class ChangeEvent { return timestamp; } + public String getIndex() { + return index; + } + public String getType() { return type; } @@ -85,4 +91,6 @@ public class ChangeEvent { public BytesReference getSource() { return source; } + + } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java new file mode 100644 index 00000000..af8ba091 --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeListener.java @@ -0,0 +1,6 @@ +package org.duniter.elasticsearch.service.changes; + +public interface ChangeListener { + String getId(); + void onChanges(String message); +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeRegister.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java similarity index 75% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeRegister.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java index 4fbeba35..56487c38 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeRegister.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.websocket; +package org.duniter.elasticsearch.service.changes; /* * #%L @@ -50,63 +50,32 @@ import org.elasticsearch.index.indexing.IndexingOperationListener; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.indices.IndicesLifecycle; import org.elasticsearch.indices.IndicesService; -import org.glassfish.tyrus.server.Server; import org.joda.time.DateTime; -import javax.websocket.DeploymentException; import java.io.IOException; -import java.security.AccessController; -import java.security.PrivilegedAction; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -public class ChangeRegister { +public class ChangeService { - private static final String SETTING_PRIMARY_SHARD_ONLY = "changes.primaryShardOnly"; - private static final String SETTING_PORT = "changes.port"; - private static final String SETTING_LISTEN_SOURCE = "changes.listenSource"; + private static final String SETTING_PRIMARY_SHARD_ONLY = "duniter.changes.primaryShardOnly"; + private static final String SETTING_LISTEN_SOURCE = "duniter.changes.listenSource"; - private final ESLogger log = Loggers.getLogger(ChangeRegister.class); + private final ESLogger log = Loggers.getLogger(ChangeService.class); - private static final Map<String, WebSocketServerEndPoint> LISTENERS = new HashMap<String, WebSocketServerEndPoint>(); + private static final Map<String, ChangeListener> LISTENERS = new HashMap<>(); @Inject - public ChangeRegister(final Settings settings, IndicesService indicesService) { + public ChangeService(final Settings settings, IndicesService indicesService) { final boolean allShards = !settings.getAsBoolean(SETTING_PRIMARY_SHARD_ONLY, Boolean.FALSE); - final int port = settings.getAsInt(SETTING_PORT, 9400); final String[] sourcesStr = settings.getAsArray(SETTING_LISTEN_SOURCE, new String[]{"*"}); final Set<ChangeSource> sources = new HashSet<>(); for(String sourceStr : sourcesStr) { sources.add(new ChangeSource(sourceStr)); } - final Server server = new Server("localhost", port, "/ws", null, WebSocketServerEndPoint.class) ; - - try { - log.info("Starting WebSocketServerEndPoint server"); - AccessController.doPrivileged(new PrivilegedAction() { - @Override - public Object run() { - try { - // Tyrus tries to load the server code using reflection. In Elasticsearch 2.x Java - // security manager is used which breaks the reflection code as it can't find the class. - // This is a workaround for that - Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); - server.start(); - return null; - } catch (DeploymentException e) { - throw new RuntimeException("Failed to start server", e); - } - } - }); - log.info("WebSocketServerEndPoint server started"); - } catch (Exception e) { - log.error("Failed to start WebSocketServerEndPoint server",e); - throw new RuntimeException(e); - } - indicesService.indicesLifecycle().addListener(new IndicesLifecycle.Listener() { @Override public void afterIndexShardStarted(IndexShard indexShard) { @@ -117,8 +86,9 @@ public class ChangeRegister { @Override public void postCreate(Engine.Create create) { ChangeEvent change=new ChangeEvent( - create.id(), + indexName, create.type(), + create.id(), new DateTime(), ChangeEvent.Operation.CREATE, create.version(), @@ -131,8 +101,9 @@ public class ChangeRegister { @Override public void postDelete(Engine.Delete delete) { ChangeEvent change=new ChangeEvent( - delete.id(), + indexName, delete.type(), + delete.id(), new DateTime(), ChangeEvent.Operation.DELETE, delete.version(), @@ -146,8 +117,9 @@ public class ChangeRegister { public void postIndex(Engine.Index index) { ChangeEvent change=new ChangeEvent( - index.id(), + indexName, index.type(), + index.id(), new DateTime(), ChangeEvent.Operation.INDEX, index.version(), @@ -205,17 +177,15 @@ public class ChangeRegister { } builder.endObject(); - - message = builder.string(); } catch (IOException e) { log.error("Failed to write JSON", e); return; } - for (WebSocketServerEndPoint listener : LISTENERS.values()) { + for (ChangeListener listener : LISTENERS.values()) { try { - listener.sendMessage(message); + listener.onChanges(message); } catch (Exception e) { log.error("Failed to send message", e); } @@ -230,11 +200,13 @@ public class ChangeRegister { }); } - public static void registerListener(WebSocketServerEndPoint webSocket) { - LISTENERS.put(webSocket.getId(), webSocket); + public static void registerListener(ChangeListener listener) { + LISTENERS.put(listener.getId(), listener); } - public static void unregisterListener(WebSocketServerEndPoint webSocket) { - LISTENERS.remove(webSocket.getId()); + public static void unregisterListener(ChangeListener listener) { + LISTENERS.remove(listener.getId()); } + + } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeSource.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java similarity index 97% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeSource.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java index 9cf7a179..6f79a507 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangeSource.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeSource.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.websocket; +package org.duniter.elasticsearch.service.changes; /* * #%L diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java new file mode 100644 index 00000000..cfe6a16c --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeUtils.java @@ -0,0 +1,65 @@ +package org.duniter.elasticsearch.service.changes; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonSyntaxException; +import org.duniter.core.exception.TechnicalException; +import org.duniter.elasticsearch.exception.InvalidFormatException; +import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.json.JsonXContent; +import org.joda.time.DateTime; + +import java.io.IOException; + +/** + * Created by blavenie on 30/11/16. + */ +public class ChangeUtils { + + public static ChangeEvent fromJson(ObjectMapper objectMapper, String json) { + try { + JsonNode actualObj = objectMapper.readTree(json); + String index = actualObj.get("_index").asText(); + String type = actualObj.get("_type").asText(); + String id = actualObj.get("_id").asText(); + DateTime timestamp = new DateTime(actualObj.get("_timestamp").asLong()); + ChangeEvent.Operation operation = ChangeEvent.Operation.valueOf(actualObj.get("_operation").asText()); + long version = actualObj.get("_version").asLong(); + + JsonNode sourceNode = actualObj.get("_source"); + BytesReference source = null; + if (sourceNode != null) { + // TODO : fill bytes reference from source + //source = sourceNode. + } + + ChangeEvent event = new ChangeEvent(index, type, id, timestamp, operation, version, source); + return event; + } catch (IOException | JsonSyntaxException e) { + throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); + } + } + + public static String toJson(ChangeEvent change) { + try { + XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new BytesStreamOutput()); + builder.startObject() + .field("_index", change.getIndex()) + .field("_type", change.getType()) + .field("_id", change.getId()) + .field("_timestamp", change.getTimestamp()) + .field("_version", change.getVersion()) + .field("_operation", change.getOperation().toString()); + if (change.getSource() != null) { + builder.rawField("_source", change.getSource()); + } + builder.endObject(); + + return builder.string(); + } catch (IOException e) { + throw new TechnicalException("Error while generating JSON from change event", e); + } + } +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java similarity index 94% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java index b024cbc0..85944904 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java @@ -98,9 +98,9 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> { } /** - * Schedules an action when node is started (all services and modules ready) + * Schedules an rest when node is started (all services and modules ready) * - * @param job the action to execute when node started + * @param job the rest to execute when node started * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled */ public void scheduleOnStarted(Runnable job) { @@ -109,9 +109,9 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> { } /** - * Schedules an action when cluster is ready + * Schedules an rest when cluster is ready * - * @param job the action to execute + * @param job the rest to execute * @param expectedStatus expected health status, to run the job * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled */ @@ -127,9 +127,9 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> { } /** - * Schedules an action that runs on the scheduler thread, after a delay. + * Schedules an rest that runs on the scheduler thread, after a delay. * - * @param command the action to take + * @param command the rest to take * @param interval the delay interval * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled */ @@ -138,9 +138,9 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> { } /** - * Schedules a periodic action that always runs on the scheduler thread. + * Schedules a periodic rest that always runs on the scheduler thread. * - * @param command the action to take + * @param command the rest to take * @param interval the delay interval * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled */ diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/Desktop.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/Desktop.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/Desktop.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/Desktop.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/DesktopPower.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/DesktopPower.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/DesktopPower.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/DesktopPower.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/WindowsPower.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/WindowsPower.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/WindowsPower.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/WindowsPower.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/handle/CWPSSTRUCT.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/handle/CWPSSTRUCT.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/handle/CWPSSTRUCT.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/handle/CWPSSTRUCT.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/handle/HANDLER_ROUTINE.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/handle/HANDLER_ROUTINE.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/handle/HANDLER_ROUTINE.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/handle/HANDLER_ROUTINE.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/handle/WNDPROC.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/handle/WNDPROC.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/handle/WNDPROC.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/handle/WNDPROC.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/libs/Kernel32Ex.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/libs/Kernel32Ex.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/libs/Kernel32Ex.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/libs/Kernel32Ex.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/GetLastErrorException.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/GetLastErrorException.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/GetLastErrorException.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/GetLastErrorException.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/WNDCLASSEXWrap.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/WNDCLASSEXWrap.java similarity index 100% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/WNDCLASSEXWrap.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/util/os/win/wrap/WNDCLASSEXWrap.java diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServerEndPoint.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketChangeEndPoint.java similarity index 84% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServerEndPoint.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketChangeEndPoint.java index eb6b2e90..23e983af 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServerEndPoint.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketChangeEndPoint.java @@ -38,6 +38,8 @@ package org.duniter.elasticsearch.websocket; limitations under the License. */ +import org.duniter.elasticsearch.service.changes.ChangeListener; +import org.duniter.elasticsearch.service.changes.ChangeService; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; @@ -45,22 +47,24 @@ import javax.websocket.*; import javax.websocket.server.ServerEndpoint; @ServerEndpoint(value = "/_changes") -public class WebSocketServerEndPoint { +public class WebsocketChangeEndPoint implements ChangeListener{ - private final ESLogger log = Loggers.getLogger(WebSocketServerEndPoint.class); + private final ESLogger log = Loggers.getLogger(WebsocketChangeEndPoint.class); private Session session; @OnOpen public void onOpen(Session session) { log.info("Connected ... " + session.getId()); this.session = session; - ChangeRegister.registerListener(this); + ChangeService.registerListener(this); } - public void sendMessage(String message) { + @Override + public void onChanges(String message) { session.getAsyncRemote().sendText(message); } + @Override public String getId() { return session == null ? null : session.getId(); } @@ -73,7 +77,7 @@ public class WebSocketServerEndPoint { @OnClose public void onClose(CloseReason reason) { log.info("Closing websocket: "+reason); - ChangeRegister.unregisterListener(this); + ChangeService.unregisterListener(this); this.session = null; } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangesModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketModule.java similarity index 87% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangesModule.java rename to duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketModule.java index 4e02183b..e3472356 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/websocket/ChangesModule.java +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketModule.java @@ -42,12 +42,12 @@ import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; -public class ChangesModule extends AbstractModule { - private final ESLogger log = Loggers.getLogger(ChangesModule.class); +public class WebsocketModule extends AbstractModule { + private final ESLogger log = Loggers.getLogger(WebsocketModule.class); @Override protected void configure() { - log.debug("Binding Changes Module"); - bind(ChangeRegister.class).asEagerSingleton(); + log.debug("Binding websocket Module"); + bind(WebsocketServer.class).asEagerSingleton(); } } diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketServer.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketServer.java new file mode 100644 index 00000000..f3a44ae8 --- /dev/null +++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/websocket/WebsocketServer.java @@ -0,0 +1,86 @@ +package org.duniter.elasticsearch.websocket; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +/* + Copyright 2015 ForgeRock AS + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import org.duniter.elasticsearch.PluginSettings; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.glassfish.tyrus.server.Server; + +import javax.websocket.DeploymentException; +import java.security.AccessController; +import java.security.PrivilegedAction; + +public class WebsocketServer { + + private final ESLogger log = Loggers.getLogger(WebsocketServer.class); + + @Inject + public WebsocketServer(final PluginSettings pluginSettings) { + final String host = pluginSettings.getWebSocketHost(); + final int port = pluginSettings.getWebSocketPort(); + + final Server server = new Server(host, port, "/ws", null, WebsocketChangeEndPoint.class) ; + + try { + log.info("Starting websocket server"); + AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + try { + // Tyrus tries to load the server code using reflection. In Elasticsearch 2.x Java + // security manager is used which breaks the reflection code as it can't find the class. + // This is a workaround for that + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + server.start(); + return null; + } catch (DeploymentException e) { + throw new RuntimeException("Failed to start server", e); + } + } + }); + log.info("Websocket server started"); + } catch (Exception e) { + log.error("Failed to start Websocket server",e); + throw new RuntimeException(e); + } + } + +} diff --git a/duniter4j-elasticsearch/src/main/resources/META-INF/services/javax.websocket.ContainerProvider b/duniter4j-es-core/src/main/resources/META-INF/services/javax.websocket.ContainerProvider similarity index 100% rename from duniter4j-elasticsearch/src/main/resources/META-INF/services/javax.websocket.ContainerProvider rename to duniter4j-es-core/src/main/resources/META-INF/services/javax.websocket.ContainerProvider diff --git a/duniter4j-elasticsearch/src/main/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean similarity index 100% rename from duniter4j-elasticsearch/src/main/resources/META-INF/services/org.duniter.core.beans.Bean rename to duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean diff --git a/duniter4j-elasticsearch/src/main/resources/META-INF/services/org.nuiton.config.ApplicationConfigProvider b/duniter4j-es-core/src/main/resources/META-INF/services/org.nuiton.config.ApplicationConfigProvider similarity index 100% rename from duniter4j-elasticsearch/src/main/resources/META-INF/services/org.nuiton.config.ApplicationConfigProvider rename to duniter4j-es-core/src/main/resources/META-INF/services/org.nuiton.config.ApplicationConfigProvider diff --git a/duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_en_GB.properties b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties similarity index 92% rename from duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_en_GB.properties rename to duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties index d5a57ab3..a6067b35 100644 --- a/duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_en_GB.properties +++ b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties @@ -34,10 +34,6 @@ duniter4j.config.option.tasks.queueCapacity.description= duniter4j.config.option.temp.directory.description= duniter4j.config.option.version.description= duniter4j.config.parse.error= -duniter4j.event.NODE_STARTED=Node started on cluster Duniter4j ES [%s] -duniter4j.event.subject.ERROR=[%s] Error message -duniter4j.event.subject.INFO=[%s] Information message -duniter4j.event.subject.WARN=[%s] Warning message duniter4j.executor.task.waitingExecution= duniter4j.job.stopped= duniter4j.job.stopping= diff --git a/duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_fr_FR.properties b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties similarity index 91% rename from duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_fr_FR.properties rename to duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties index 5c2ac7bf..7e587779 100644 --- a/duniter4j-elasticsearch/src/main/resources/i18n/duniter4j-elasticsearch_fr_FR.properties +++ b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties @@ -1,4 +1,4 @@ -duniter4j-elasticsearch.config= +duniter4j-es-core.config= duniter4j.blockIndexerService.detectFork.invalidBlock=[%s] [%s] Detecting fork\: block \#%s -> new hash [%s] duniter4j.blockIndexerService.detectFork.invalidBlockchain=[%s] [%s] Peer has another blockchain (no common blocks \!). Skipping block \#%s - hash [%s]. duniter4j.blockIndexerService.detectFork.remoteBlockNotFound=[%s] [%s] Unable to get block \#%s from peer\: %s @@ -34,10 +34,6 @@ duniter4j.config.option.tasks.queueCapacity.description= duniter4j.config.option.temp.directory.description= duniter4j.config.option.version.description= duniter4j.config.parse.error= -duniter4j.event.NODE_STARTED=Noeud démarré sur le cluster Duniter4j ES [%s] -duniter4j.event.subject.ERROR=%s Message d'erreur -duniter4j.event.subject.INFO=%s Message d'information -duniter4j.event.subject.WARN=%s Message d'avertissement duniter4j.executor.task.waitingExecution= duniter4j.job.stopped= duniter4j.job.stopping= diff --git a/duniter4j-elasticsearch/src/main/resources/market-categories-bulk-insert.json b/duniter4j-es-core/src/main/resources/market-categories-bulk-insert.json similarity index 100% rename from duniter4j-elasticsearch/src/main/resources/market-categories-bulk-insert.json rename to duniter4j-es-core/src/main/resources/market-categories-bulk-insert.json diff --git a/duniter4j-elasticsearch/src/main/resources/plugin-security.policy b/duniter4j-es-core/src/main/resources/plugin-security.policy similarity index 73% rename from duniter4j-elasticsearch/src/main/resources/plugin-security.policy rename to duniter4j-es-core/src/main/resources/plugin-security.policy index 08f3659d..3cc974ff 100644 --- a/duniter4j-elasticsearch/src/main/resources/plugin-security.policy +++ b/duniter4j-es-core/src/main/resources/plugin-security.policy @@ -1,4 +1,4 @@ -grant codeBase "file:${es.path.home}/plugins/duniter4j-elasticsearch/"{ +grant codeBase "file:${es.path.home}/plugins/duniter4j-es-core/"{ permission java.io.FilePermission "/etc/ld.so.conf", "read"; permission java.io.FilePermission "/etc/ld.so.conf.d/*.conf", "read"; permission java.io.FilePermission "/usr/local/lib/*", "read"; diff --git a/duniter4j-elasticsearch/src/main/resources/registry-categories-bulk-insert.json b/duniter4j-es-core/src/main/resources/registry-categories-bulk-insert.json similarity index 100% rename from duniter4j-elasticsearch/src/main/resources/registry-categories-bulk-insert.json rename to duniter4j-es-core/src/main/resources/registry-categories-bulk-insert.json diff --git a/duniter4j-es-core/src/test/es-home/config/elasticsearch.yml b/duniter4j-es-core/src/test/es-home/config/elasticsearch.yml new file mode 100644 index 00000000..828ddbd0 --- /dev/null +++ b/duniter4j-es-core/src/test/es-home/config/elasticsearch.yml @@ -0,0 +1,178 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please see the documentation for further information on configuration options: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html> +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +# cluster.name: my-application +cluster.name: duniter4j-elasticsearch-TEST +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +# node.name: node-1 +# +# Add custom attributes to the node: +# +# node.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +# path.data: /path/to/data +# +# Path to log files: +# +# path.logs: /path/to/logs +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +# bootstrap.mlockall: true +# +# Make sure that the `ES_HEAP_SIZE` environment variable is set to about half the memory +# available on the system and that the owner of the process is allowed to use this limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# Set the bind address to a specific IP (IPv4 or IPv6): +# +# network.host: 192.168.233.1 +# +# Set a custom port for HTTP: +# +# http.port: 9200-9300 + +http.cors.allow-origin: "/.*/" +http.cors.enabled: true + +# Internal transport layer +# +# transport.tcp.port: 9210-9220 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html> +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when new node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +# discovery.zen.ping.unicast.hosts: ["host1", "host2"] +#discovery.zen.ping.unicast.hosts: ["127.0.0.1", ""] +# +# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1): +# +# discovery.zen.minimum_master_nodes: 3 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html> +# +# ---------------------------------- Gateway ----------------------------------- +# +# Block initial recovery after a full cluster restart until N nodes are started: +# +# gateway.recover_after_nodes: 3 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-gateway.html> +# +# ---------------------------------- Various ----------------------------------- +# +# Disable starting multiple nodes on a single system: +# +# node.max_local_storage_nodes: 1 +# +# Require explicit names when deleting indices: +# +# rest.destructive_requires_name: true + +security.manager.enabled: false + +# +# ---------------------------------- Duniter4j --------------------------------- +# +# Disbale duniter4j plugin +# +# duniter.enabled: false +# +# Reset and reload all Duniter4j data at startup - DO SET to true in production +# +# duniter.indices.reload: true +# +# Default string analyzer +# +duniter.string.analyzer: french +# +# Enabling node blockchain synchronization +# +duniter.blockchain.sync.enable: false +# +# Duniter node to synchronize +# +duniter.host: cgeek.fr +duniter.port: 9330 +# +# ---------------------------------- Duniter4j security ------------------------- +# +duniter.keyring.salt: abc +duniter.keyring.password: def + +# Enable security, to disable HTTP access to the default ES admin API +# +duniter.security.enable: false +# +# Security token prefix (default: 'duniter-') +# +# duniter.auth.token.prefix: duniter- +# +# Token validity duration, in seconds (default: 600) +# +# duniter.auth.tokenValidityDuration: 3600 # = 1hour +# +# ---------------------------------- Duniter4j P2P sync ------------------------- +# +# Should synchronize data using P2P +# +duniter.data.sync.enable: false +#duniter.data.sync.host: data.duniter.fr +#duniter.data.sync.port: 80 + +# ---------------------------------- Duniter4j SMTP server ------------------------- +# +# SMTP server configuration (host and port) +# +#duniter.mail.smtp.host: localhost +#duniter.mail.smtp.port: 25 +# +# Mail 'from' address +# +#duniter.mail.from: no-reply@domain.com +duniter.mail.from: root@EIS-DEV +# +# Mail: admin address +# +#duniter.mail.admin: user@domain.com +duniter.mail.admin: blavenie@EIS-DEV +# +# Mail subject prefix +# +#duniter.mail.subject.prefix: [Duniter4j ES] + +duniter.changes.listenSource: */block +duniter.ws.port: 9400 diff --git a/duniter4j-es-core/src/test/es-home/config/logging.yml b/duniter4j-es-core/src/test/es-home/config/logging.yml new file mode 100644 index 00000000..15cfa3e1 --- /dev/null +++ b/duniter4j-es-core/src/test/es-home/config/logging.yml @@ -0,0 +1,97 @@ +# you can override this using by setting a system property, for example -Des.logger.level=DEBUG +es.logger.level: INFO +rootLogger: ${es.logger.level}, console, file +logger: + # log rest execution errors for easier debugging + action: DEBUG + + # deprecation logging, turn to DEBUG to see them + deprecation: INFO, deprecation_log_file + + # reduce the logging for aws, too much is logged under the default INFO + com.amazonaws: WARN + # aws will try to do some sketchy JMX stuff, but its not needed. + com.amazonaws.jmx.SdkMBeanRegistrySupport: ERROR + com.amazonaws.metrics.AwsSdkMetrics: ERROR + + org.apache.http: INFO + + org.duniter: INFO + + org.duniter.elasticsearch: DEBUG + + duniter : DEBUG + duniter.network.p2p: TRACE + + security: DEBUG + + org.nuiton.i18n: WARN + org.nuiton.config: WARN + + # gateway + #gateway: DEBUG + #index.gateway: DEBUG + + # peer shard recovery + #indices.recovery: DEBUG + + # discovery + #discovery: TRACE + + index.search.slowlog: TRACE, index_search_slow_log_file + index.indexing.slowlog: TRACE, index_indexing_slow_log_file + +additivity: + index.search.slowlog: false + index.indexing.slowlog: false + deprecation: false + +appender: + console: + type: console + layout: + type: consolePattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %.10000m%n" + + # Use the following log4j-extras RollingFileAppender to enable gzip compression of log files. + # For more information see https://logging.apache.org/log4j/extras/apidocs/org/apache/log4j/rolling/RollingFileAppender.html + #file: + #type: extrasRollingFile + #file: ${path.logs}/${cluster.name}.log + #rollingPolicy: timeBased + #rollingPolicy.FileNamePattern: ${path.logs}/${cluster.name}.log.%d{yyyy-MM-dd}.gz + #layout: + #type: pattern + #conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + deprecation_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_deprecation.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + index_search_slow_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_index_search_slowlog.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + index_indexing_slow_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_index_indexing_slowlog.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" diff --git a/duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/TestFixtures.java b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/TestFixtures.java similarity index 100% rename from duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/TestFixtures.java rename to duniter4j-es-core/src/test/java/org/duniter/elasticsearch/TestFixtures.java diff --git a/duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/TestResource.java b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/TestResource.java similarity index 100% rename from duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/TestResource.java rename to duniter4j-es-core/src/test/java/org/duniter/elasticsearch/TestResource.java diff --git a/duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java b/duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java similarity index 100% rename from duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java rename to duniter4j-es-core/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java diff --git a/duniter4j-elasticsearch/src/test/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-es-core/src/test/resources/META-INF/services/org.duniter.core.beans.Bean similarity index 100% rename from duniter4j-elasticsearch/src/test/resources/META-INF/services/org.duniter.core.beans.Bean rename to duniter4j-es-core/src/test/resources/META-INF/services/org.duniter.core.beans.Bean diff --git a/duniter4j-elasticsearch/src/test/resources/curl_test.sh b/duniter4j-es-core/src/test/resources/curl_test.sh similarity index 100% rename from duniter4j-elasticsearch/src/test/resources/curl_test.sh rename to duniter4j-es-core/src/test/resources/curl_test.sh diff --git a/duniter4j-elasticsearch/src/test/resources/duniter4j-elasticsearch-localhost-node.properties b/duniter4j-es-core/src/test/resources/duniter4j-elasticsearch-localhost-node.properties similarity index 100% rename from duniter4j-elasticsearch/src/test/resources/duniter4j-elasticsearch-localhost-node.properties rename to duniter4j-es-core/src/test/resources/duniter4j-elasticsearch-localhost-node.properties diff --git a/duniter4j-elasticsearch/src/test/resources/duniter4j-elasticsearch-test.properties b/duniter4j-es-core/src/test/resources/duniter4j-elasticsearch-test.properties similarity index 100% rename from duniter4j-elasticsearch/src/test/resources/duniter4j-elasticsearch-test.properties rename to duniter4j-es-core/src/test/resources/duniter4j-elasticsearch-test.properties diff --git a/duniter4j-elasticsearch/src/test/resources/log4j.properties b/duniter4j-es-core/src/test/resources/log4j.properties similarity index 100% rename from duniter4j-elasticsearch/src/test/resources/log4j.properties rename to duniter4j-es-core/src/test/resources/log4j.properties diff --git a/duniter4j-elasticsearch/src/test/resources/registry-test-records.json b/duniter4j-es-core/src/test/resources/registry-test-records.json similarity index 100% rename from duniter4j-elasticsearch/src/test/resources/registry-test-records.json rename to duniter4j-es-core/src/test/resources/registry-test-records.json diff --git a/duniter4j-es-gchange/pom.xml b/duniter4j-es-gchange/pom.xml new file mode 100644 index 00000000..a76429f4 --- /dev/null +++ b/duniter4j-es-gchange/pom.xml @@ -0,0 +1,129 @@ +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.duniter</groupId> + <artifactId>duniter4j</artifactId> + <version>0.3.5-SNAPSHOT</version> + </parent> + + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-gchange</artifactId> + <packaging>jar</packaging> + <name>Duniter4j :: ElasticSearch GChange plugin</name> + + <properties> + + <!-- i18n configuration --> + <i18n.bundleOutputName>duniter4j-es-gchange-i18n</i18n.bundleOutputName> + <i18n.generateCsvFile>true</i18n.generateCsvFile> + <i18n.bundleCsvFile> + ${maven.gen.dir}/resources/META-INF/${i18n.bundleOutputName}.csv + </i18n.bundleCsvFile> + <config.i18nBundleName>${i18n.bundleOutputName}</config.i18nBundleName> + </properties> + + <dependencies> + <dependency> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-core</artifactId> + <version>${project.version}</version> + </dependency> + <dependency> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-user</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + + <!-- Elastic Search --> + <dependency> + <groupId>org.elasticsearch</groupId> + <artifactId>elasticsearch</artifactId> + <scope>provided</scope> + </dependency> + + <!-- Unit test --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/filtered-resources</directory> + <filtering>true</filtering> + <includes> + <include>*.config</include> + <include>**/*.properties</include> + </includes> + </resource> + <resource> + <directory>src/main/resources</directory> + <filtering>false</filtering> + </resource> + </resources> + + <plugins> + <plugin> + <groupId>org.nuiton.i18n</groupId> + <artifactId>i18n-maven-plugin</artifactId> + + <executions> + <execution> + <id>scan-sources</id> + <configuration> + <entries> + <entry> + <specificGoal>parserValidation</specificGoal> + <basedir>${maven.src.dir}/main/java/</basedir> + <includes> + <param>**/**-validation.xml</param> + </includes> + </entry> + </entries> + </configuration> + <goals> + <goal>parserJava</goal> + <goal>parserValidation</goal> + <goal>gen</goal> + </goals> + </execution> + <execution> + <id>make-bundle</id> + <goals> + <goal>bundle</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <id>assembly-plugin</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <attach>true</attach> + <appendAssemblyId>false</appendAssemblyId> + <finalName>${project.artifactId}-${project.version}</finalName> + <descriptors> + <descriptor> + ${basedir}/src/main/assembly/plugin.xml + </descriptor> + </descriptors> + <skipAssembly>${assembly.skip}</skipAssembly> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> diff --git a/duniter4j-es-gchange/src/license/THIRD-PARTY.properties b/duniter4j-es-gchange/src/license/THIRD-PARTY.properties new file mode 100644 index 00000000..8610ec5b --- /dev/null +++ b/duniter4j-es-gchange/src/license/THIRD-PARTY.properties @@ -0,0 +1,26 @@ +# Generated by org.codehaus.mojo.license.AddThirdPartyMojo +#------------------------------------------------------------------------------- +# Already used licenses in project : +# - ASL, version 2 +# - Apache License 2.0 +# - Apache License Version 2.0 +# - BSD License +# - CC0 1.0 Universal +# - COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) Version 1.0 +# - Eclipse Public License 1.0 +# - General Public License (GPL) v3 +# - Indiana University Extreme! Lab Software License, vesion 1.1.1 +# - LGPL, version 2.1 +# - Lesser General Public License (LGPL) v 3.0 +# - Lesser General Public License (LPGL) +# - Lesser General Public License (LPGL) v 2.1 +# - MIT License +# - New BSD License +# - Public Domain, per Creative Commons CC0 +# - The Apache Software License, Version 2.0 +#------------------------------------------------------------------------------- +# Please fill the missing licenses for dependencies : +# +# +#Tue Jan 05 15:24:57 CET 2016 +commons-primitives--commons-primitives--1.0=The Apache Software License, Version 2.0 diff --git a/duniter4j-es-gchange/src/main/assembly/plugin.xml b/duniter4j-es-gchange/src/main/assembly/plugin.xml new file mode 100644 index 00000000..004bbfa7 --- /dev/null +++ b/duniter4j-es-gchange/src/main/assembly/plugin.xml @@ -0,0 +1,44 @@ +<?xml version="1.0"?> +<assembly> + <id>plugin</id> + + + <formats> + <format>zip</format> + </formats> + + <includeBaseDirectory>false</includeBaseDirectory> + + <dependencySets> + <dependencySet> + <outputDirectory>/</outputDirectory> + <useProjectArtifact>true</useProjectArtifact> + <useTransitiveFiltering>true</useTransitiveFiltering> + <excludes> + <exclude>org.duniter:duniter4j-es-core</exclude> + <exclude>org.duniter:duniter4j-es-user</exclude> + <exclude>org.elasticsearch:elasticsearch</exclude> + <exclude>net.java.dev.jna:jna</exclude> + <exclude>com.fasterxml.jackson.core:jackson-core</exclude> + <exclude>log4j:log4j</exclude> + </excludes> + </dependencySet> + </dependencySets> + + <fileSets> + <fileSet> + <includes> + <include>LICENSE</include> + </includes> + </fileSet> + + <fileSet> + <directory>target/classes</directory> + <outputDirectory/> + <includes> + <include>plugin-descriptor.properties</include> + <include>plugin-security.policy</include> + </includes> + </fileSet> + </fileSets> +</assembly> \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/filtered-resources/log4j.properties b/duniter4j-es-gchange/src/main/filtered-resources/log4j.properties new file mode 100644 index 00000000..7b6667b1 --- /dev/null +++ b/duniter4j-es-gchange/src/main/filtered-resources/log4j.properties @@ -0,0 +1,32 @@ + +# Global logging configuration +log4j.rootLogger=ERROR, stdout, file +#log4j.rootLogger=ERROR, stdout + +# Console output +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p %m%n + +# Duniter4j levels +log4j.logger.org.duniter=INFO +#log4j.logger.org.duniter.core.client=DEBUG +#log4j.logger.org.duniter.core.client.service=DEBUG +log4j.logger.org.duniter.elasticsearch=DEBUG + +# Other frameworks levels +log4j.logger.org.nuiton.util=WARN +log4j.logger.org.nuiton.config=WARN +log4j.logger.org.nuiton.converter=WARN +log4j.logger.org.nuiton.i18n=ERROR +log4j.logger.org.elasticsearch=WARN +#log4j.logger.org.elasticsearch=INFO + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.file=${duniter4j.log.file} +log4j.appender.file.MaxFileSize=10MB +log4j.appender.file.MaxBackupIndex=4 + +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - [%t] %m%n + diff --git a/duniter4j-es-gchange/src/main/filtered-resources/plugin-descriptor.properties b/duniter4j-es-gchange/src/main/filtered-resources/plugin-descriptor.properties new file mode 100644 index 00000000..821d0eeb --- /dev/null +++ b/duniter4j-es-gchange/src/main/filtered-resources/plugin-descriptor.properties @@ -0,0 +1,9 @@ +name=gchange +description=Plugin for Gchange API +version=${project.version} +site=false +jvm=true +classname=org.duniter.elasticsearch.gchange.Plugin +java.version=1.7 +elasticsearch.version=2.3.3 +isolated=true diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java new file mode 100644 index 00000000..c7b1067c --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java @@ -0,0 +1,84 @@ +package org.duniter.elasticsearch.gchange; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import com.google.common.collect.Lists; +import org.duniter.elasticsearch.gchange.rest.RestModule; +import org.duniter.elasticsearch.gchange.service.ServiceModule; +import org.elasticsearch.common.component.LifecycleComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.Settings; + +import java.util.Collection; + +public class Plugin extends org.elasticsearch.plugins.Plugin { + + private ESLogger log = ESLoggerFactory.getLogger(Plugin.class.getName()); + + private boolean enable; + + @Inject public Plugin(Settings settings) { + this.enable = settings.getAsBoolean("gchange.enabled", true); + } + + @Override + public String name() { + return "gchange"; + } + + @Override + public String description() { + return "ElasticSearch Gchange Plugin"; + } + + @Override + public Collection<Module> nodeModules() { + Collection<Module> modules = Lists.newArrayList(); + if (!enable) { + log.warn(description() + " has been disabled."); + return modules; + } + modules.add(new RestModule()); + modules.add(new ServiceModule()); + + return modules; + } + + @Override + public Collection<Class<? extends LifecycleComponent>> nodeServices() { + Collection<Class<? extends LifecycleComponent>> components = Lists.newArrayList(); + if (!enable) { + return components; + } + components.add(PluginSettings.class); + components.add(PluginInit.class); + return components; + } + + /* -- protected methods -- */ + + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginInit.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginInit.java new file mode 100644 index 00000000..020dda5d --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginInit.java @@ -0,0 +1,134 @@ +package org.duniter.elasticsearch.gchange; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.gchange.service.MarketService; +import org.duniter.elasticsearch.gchange.service.RegistryService; +import org.duniter.elasticsearch.gchange.service.SynchroService; +import org.duniter.elasticsearch.threadpool.ThreadPool; +import org.duniter.elasticsearch.user.service.event.UserEvent; +import org.duniter.elasticsearch.user.service.event.UserEventCodes; +import org.duniter.elasticsearch.user.service.event.UserEventService; +import org.elasticsearch.client.Client; +import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.inject.Injector; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.Loggers; +import org.elasticsearch.common.settings.Settings; + +/** + * Created by blavenie on 17/06/16. + */ +public class PluginInit extends AbstractLifecycleComponent<PluginInit> { + + private final PluginSettings pluginSettings; + private final ThreadPool threadPool; + private final Injector injector; + private final static ESLogger logger = Loggers.getLogger("gchange"); + private final Client client; + private final String clusterName; + + @Inject + public PluginInit(Client client, Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) { + super(settings); + this.pluginSettings = pluginSettings; + this.threadPool = threadPool; + this.injector = injector; + this.client = client; + this.clusterName = settings.get("cluster.name"); + } + + @Override + protected void doStart() { + threadPool.scheduleOnClusterHealthStatus(() -> { + createIndices(); + + // Waiting cluster back to GREEN or YELLOW state, before synchronize + threadPool.scheduleOnClusterHealthStatus(() -> { + synchronize(); + }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN); + }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN); + + // When started + threadPool.scheduleOnStarted(() -> { + // Notify admin + injector.getInstance(UserEventService.class) + .notifyAdmin(new UserEvent( + UserEvent.EventType.INFO, + UserEventCodes.NODE_STARTED.name(), + new String[]{clusterName})); + }); + } + + @Override + protected void doStop() { + + } + + @Override + protected void doClose() { + + } + + protected void createIndices() { + + boolean reloadIndices = pluginSettings.reloadIndices(); + + if (reloadIndices) { + if (logger.isInfoEnabled()) { + logger.info("Reloading all Gchange indices..."); + } + injector.getInstance(RegistryService.class) + .deleteIndex() + .createIndexIfNotExists(); + injector.getInstance(MarketService.class) + .deleteIndex() + .createIndexIfNotExists(); + + if (logger.isInfoEnabled()) { + logger.info("Reloading all Gchange indices... [OK]"); + } + } + else { + if (logger.isInfoEnabled()) { + logger.info("Checking Gchange indices..."); + } + injector.getInstance(RegistryService.class).createIndexIfNotExists(); + injector.getInstance(MarketService.class).createIndexIfNotExists(); + + if (logger.isInfoEnabled()) { + logger.info("Checking Gchange indices... [OK]"); + } + } + } + + protected void synchronize() { + + if (pluginSettings.enableDataSync()) { + // Synchronize + injector.getInstance(SynchroService.class).synchronize(); + } + } +} diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginSettings.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginSettings.java new file mode 100644 index 00000000..48498ce0 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/PluginSettings.java @@ -0,0 +1,67 @@ +package org.duniter.elasticsearch.gchange; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import com.google.common.collect.ImmutableSet; +import org.apache.commons.io.FileUtils; +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.config.ConfigurationOption; +import org.duniter.core.client.config.ConfigurationProvider; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.exception.TechnicalException; +import org.duniter.core.util.StringUtils; +import org.elasticsearch.common.component.*; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.nuiton.config.ApplicationConfig; +import org.nuiton.config.ApplicationConfigHelper; +import org.nuiton.config.ApplicationConfigProvider; +import org.nuiton.config.ArgumentsParserException; +import org.nuiton.i18n.I18n; +import org.nuiton.i18n.init.DefaultI18nInitializer; +import org.nuiton.i18n.init.UserI18nInitializer; + +import java.io.File; +import java.io.IOException; +import java.util.Locale; +import java.util.Set; + +import static org.nuiton.i18n.I18n.t; + +/** + * Access to configuration options + * @author Benoit Lavenier <benoit.lavenier@e-is.pro> + * @since 1.0 + */ +public class PluginSettings extends org.duniter.elasticsearch.user.PluginSettings { + + @Inject + public PluginSettings(org.elasticsearch.common.settings.Settings settings) { + super(settings); + } + + protected String getI18nBundleName() { + return "duniter4j-es-gchange-i18n"; + } +} diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/RestModule.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/RestModule.java new file mode 100644 index 00000000..4f45cec5 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/RestModule.java @@ -0,0 +1,48 @@ +package org.duniter.elasticsearch.gchange.rest; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.gchange.rest.market.*; +import org.duniter.elasticsearch.gchange.rest.registry.*; +import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.Module; + +public class RestModule extends AbstractModule implements Module { + + @Override protected void configure() { + + // Market + bind(RestMarketRecordIndexAction.class).asEagerSingleton(); + bind(RestMarketRecordUpdateAction.class).asEagerSingleton(); + bind(RestMarketCommentIndexAction.class).asEagerSingleton(); + bind(RestMarketCommentUpdateAction.class).asEagerSingleton(); + bind(RestMarketCategoryAction.class).asEagerSingleton(); + + // Registry + bind(RestRegistryRecordIndexAction.class).asEagerSingleton(); + bind(RestRegistryRecordUpdateAction.class).asEagerSingleton(); + bind(RestRegistryCommentIndexAction.class).asEagerSingleton(); + bind(RestregistryCommentUpdateAction.class).asEagerSingleton(); + bind(RestRegistryCategoryAction.class).asEagerSingleton(); + } +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java new file mode 100644 index 00000000..7aa19360 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java @@ -0,0 +1,38 @@ +package org.duniter.elasticsearch.gchange.rest.market; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.MarketService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.rest.RestRequest; + +public class RestMarketCategoryAction { + + @Inject + public RestMarketCategoryAction(RestSecurityController securityController) { + // Add security rule for category + securityController.allowIndexType(RestRequest.Method.GET, MarketService.INDEX, MarketService.RECORD_CATEGORY_TYPE); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java new file mode 100644 index 00000000..a65f154c --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.market; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.MarketService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestMarketCommentIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestMarketCommentIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + MarketService service) { + super(settings, controller, client, securityController, + MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE, + json -> service.indexCommentFromJson(json)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java new file mode 100644 index 00000000..c10f58c8 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.market; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.MarketService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestMarketCommentUpdateAction extends AbstractRestPostUpdateAction { + + @Inject + public RestMarketCommentUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + MarketService service) { + super(settings, controller, client, securityController, + MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE, + (json, id) -> service.updateCommentFromJson(json, id)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java new file mode 100644 index 00000000..55f4c5fb --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.market; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.MarketService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestMarketRecordIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestMarketRecordIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + MarketService service) { + super(settings, controller, client, securityController, + MarketService.INDEX, MarketService.RECORD_TYPE, + json -> service.indexRecordFromJson(json)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java new file mode 100644 index 00000000..9761924f --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.market; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.MarketService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestMarketRecordUpdateAction extends AbstractRestPostUpdateAction { + + @Inject + public RestMarketRecordUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + MarketService service) { + super(settings, controller, client, securityController, + MarketService.INDEX, MarketService.RECORD_TYPE, + (json, id) -> service.updateRecordFromJson(json, id)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java new file mode 100644 index 00000000..0fdb34fa --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java @@ -0,0 +1,38 @@ +package org.duniter.elasticsearch.gchange.rest.registry; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.gchange.service.RegistryService; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.rest.RestRequest; + +public class RestRegistryCategoryAction { + + @Inject + public RestRegistryCategoryAction(RestSecurityController securityController) { + // Add security rule for category + securityController.allowIndexType(RestRequest.Method.GET, RegistryService.INDEX, RegistryService.RECORD_CATEGORY_TYPE); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java new file mode 100644 index 00000000..bd004760 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.registry; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.RegistryService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestRegistryCommentIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestRegistryCommentIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + RegistryService service) { + super(settings, controller, client, securityController, + RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE, + json -> service.indexCommentFromJson(json)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java new file mode 100644 index 00000000..02c2e963 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.registry; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.RegistryService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestRegistryRecordIndexAction extends AbstractRestPostIndexAction { + + + @Inject + public RestRegistryRecordIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + RegistryService service) { + super(settings, controller, client, securityController, + RegistryService.INDEX, RegistryService.RECORD_TYPE, + json -> service.indexRecordFromJson(json)); + } +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java new file mode 100644 index 00000000..e77b9425 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.registry; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.RegistryService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestRegistryRecordUpdateAction extends AbstractRestPostUpdateAction { + + @Inject + public RestRegistryRecordUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + RegistryService service) { + super(settings, controller, client, securityController, + RegistryService.INDEX, RegistryService.RECORD_TYPE, + (json, id) -> service.updateRecordFromJson(json, id)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestregistryCommentUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestregistryCommentUpdateAction.java new file mode 100644 index 00000000..8687f463 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestregistryCommentUpdateAction.java @@ -0,0 +1,43 @@ +package org.duniter.elasticsearch.gchange.rest.registry; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.gchange.service.RegistryService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestregistryCommentUpdateAction extends AbstractRestPostUpdateAction { + + @Inject + public RestregistryCommentUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController, + RegistryService service) { + super(settings, controller, client, securityController, + RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE, + (json, id) -> service.updateCommentFromJson(json, id)); + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/registry/CitiesRegistryService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java similarity index 99% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/registry/CitiesRegistryService.java rename to duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java index 891791a8..e6f72a72 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/registry/CitiesRegistryService.java +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service.registry; +package org.duniter.elasticsearch.gchange.service; /* * #%L @@ -26,13 +26,13 @@ package org.duniter.elasticsearch.service.registry; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; +import org.apache.commons.io.FileUtils; +import org.apache.commons.io.IOUtils; import org.duniter.core.client.model.bma.gson.GsonUtils; import org.duniter.core.exception.TechnicalException; import org.duniter.core.util.StringUtils; -import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.gchange.PluginSettings; import org.duniter.elasticsearch.service.AbstractService; -import org.apache.commons.io.FileUtils; -import org.apache.commons.io.IOUtils; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java similarity index 98% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java rename to duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java index 7b1a5a11..b037fc07 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service; +package org.duniter.elasticsearch.gchange.service; /* * #%L @@ -27,7 +27,8 @@ import com.fasterxml.jackson.core.JsonProcessingException; import org.duniter.core.client.service.bma.WotRemoteService; import org.duniter.core.exception.TechnicalException; import org.duniter.core.service.CryptoService; -import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.gchange.PluginSettings; +import org.duniter.elasticsearch.service.AbstractService; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.index.IndexResponse; diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java new file mode 100644 index 00000000..3542cd47 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java @@ -0,0 +1,357 @@ +package org.duniter.elasticsearch.gchange.service; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.google.gson.Gson; +import org.duniter.core.client.model.bma.gson.GsonUtils; +import org.duniter.core.client.service.bma.BlockchainRemoteService; +import org.duniter.core.exception.TechnicalException; +import org.duniter.core.service.CryptoService; +import org.duniter.elasticsearch.gchange.PluginSettings; +import org.duniter.elasticsearch.service.AbstractService; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; +import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; + +import java.io.IOException; + +/** + * Created by Benoit on 30/03/2015. + */ +public class RegistryService extends AbstractService { + + public static final String INDEX = "registry"; + public static final String RECORD_TYPE = "record"; + public static final String RECORD_CATEGORY_TYPE = "category"; + public static final String RECORD_COMMENT_TYPE = "comment"; + private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json"; + + private final Gson gson; + private BlockchainRemoteService blockchainRemoteService; + + @Inject + public RegistryService(Client client, + PluginSettings settings, + CryptoService cryptoService, + BlockchainRemoteService blockchainRemoteService) { + super("gchange." + INDEX, client, settings, cryptoService); + this.gson = GsonUtils.newBuilder().create(); + this.blockchainRemoteService = blockchainRemoteService; + } + + /** + * Create index need for blockchain registry, if need + */ + public RegistryService createIndexIfNotExists() { + try { + if (!existsIndex(INDEX)) { + createIndex(); + + fillRecordCategories(); + } + } + catch(JsonProcessingException e) { + throw new TechnicalException(String.format("Error while creating index [%s]", INDEX)); + } + return this; + } + + /** + * Create index for registry + * @throws JsonProcessingException + */ + public RegistryService createIndex() throws JsonProcessingException { + logger.info(String.format("Creating index [%s]", INDEX)); + + CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX); + org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder() + .put("number_of_shards", 3) + .put("number_of_replicas", 1) + //.put("analyzer", createDefaultAnalyzer()) + .build(); + createIndexRequestBuilder.setSettings(indexSettings); + createIndexRequestBuilder.addMapping(RECORD_CATEGORY_TYPE, createRecordCategoryType()); + createIndexRequestBuilder.addMapping(RECORD_TYPE, createRecordType()); + createIndexRequestBuilder.addMapping(RECORD_COMMENT_TYPE, createRecordCommentType(INDEX, RECORD_COMMENT_TYPE)); + createIndexRequestBuilder.execute().actionGet(); + + return this; + } + + public RegistryService deleteIndex() { + deleteIndexIfExists(INDEX); + return this; + } + + public boolean existsIndex() { + return super.existsIndex(INDEX); + } + + public RegistryService fillRecordCategories() { + if (logger.isDebugEnabled()) { + logger.debug(String.format("[%s/%s] Fill data", INDEX, RECORD_CATEGORY_TYPE)); + } + + // Insert categories + bulkFromClasspathFile(CATEGORIES_BULK_CLASSPATH_FILE, INDEX, RECORD_CATEGORY_TYPE, + // Add order attribute + new AddSequenceAttributeHandler("order", "\\{.*\"name\".*\\}", 1)); + + return this; + } + + public String indexRecordFromJson(String json) { + return checkIssuerAndIndexDocumentFromJson(INDEX, RECORD_TYPE, json); + } + + public void updateRecordFromJson(String json, String id) { + checkIssuerAndUpdateDocumentFromJson(INDEX, RECORD_TYPE, json, id); + } + + public String indexCommentFromJson(String json) { + return checkIssuerAndIndexDocumentFromJson(INDEX, RECORD_COMMENT_TYPE, json); + } + + public void updateCommentFromJson(String json, String id) { + checkIssuerAndUpdateDocumentFromJson(INDEX, RECORD_COMMENT_TYPE, json, id); + } + + /* -- Internal methods -- */ + + public XContentBuilder createRecordType() { + String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer(); + + try { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_TYPE) + .startObject("properties") + + // title + .startObject("title") + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + + // description + .startObject("description") + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + + // creationTime + .startObject("creationTime") + .field("type", "integer") + .endObject() + + // time + .startObject("time") + .field("type", "integer") + .endObject() + + // issuer + .startObject("issuer") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // pubkey + .startObject("pubkey") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // address + .startObject("address") + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + + // city + .startObject("city") + .field("type", "string") + .endObject() + + // geoPoint + .startObject("geoPoint") + .field("type", "geo_point") + .endObject() + + // thumbnail + .startObject("thumbnail") + .field("type", "attachment") + .startObject("fields") // src + .startObject("content") // title + .field("index", "no") + .endObject() + .startObject("title") // title + .field("type", "string") + .field("store", "no") + .endObject() + .startObject("author") // title + .field("store", "no") + .endObject() + .startObject("content_type") // title + .field("store", "yes") + .endObject() + .endObject() + .endObject() + + // pictures + .startObject("pictures") + .field("type", "nested") + .field("dynamic", "false") + .startObject("properties") + .startObject("file") // file + .field("type", "attachment") + .startObject("fields") + .startObject("content") // content + .field("index", "no") + .endObject() + .startObject("title") // title + .field("type", "string") + .field("store", "yes") + .field("analyzer", stringAnalyzer) + .endObject() + .startObject("author") // author + .field("type", "string") + .field("store", "no") + .endObject() + .startObject("content_type") // content_type + .field("store", "yes") + .endObject() + .endObject() + .endObject() + .endObject() + .endObject() + + // picturesCount + .startObject("picturesCount") + .field("type", "integer") + .endObject() + + // category + .startObject("category") + .field("type", "nested") + .field("dynamic", "false") + .startObject("properties") + .startObject("id") // id + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + .startObject("parent") // parent + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + .startObject("name") // name + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + .endObject() + .endObject() + + // tags + .startObject("tags") + .field("type", "completion") + .field("search_analyzer", "simple") + .field("analyzer", "simple") + .field("preserve_separators", "false") + .endObject() + + .endObject() + .endObject().endObject(); + + return mapping; + } + catch(IOException ioe) { + throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_TYPE, ioe.getMessage()), ioe); + } + } + + public XContentBuilder createRecordCategoryType() { + try { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_CATEGORY_TYPE) + .startObject("properties") + + // name + .startObject("name") + .field("type", "string") + .field("analyzer", pluginSettings.getDefaultStringAnalyzer()) + .endObject() + + // description + /*.startObject("description") + .field("type", "string") + .endObject()*/ + + // parent + .startObject("parent") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // tags + /*.startObject("tags") + .field("type", "completion") + .field("search_analyzer", "simple") + .field("analyzer", "simple") + .field("preserve_separators", "false") + .endObject()*/ + + .endObject() + .endObject().endObject(); + + return mapping; + } + catch(IOException ioe) { + throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_CATEGORY_TYPE, ioe.getMessage()), ioe); + } + } + + /** + * + * @param jsonCategory + * @return the product id + */ + public String indexCategoryFromJson(String jsonCategory) { + if (logger.isDebugEnabled()) { + logger.debug("Indexing a category"); + } + + // Preparing indexBlocksFromNode + IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_CATEGORY_TYPE) + .setSource(jsonCategory); + + // Execute indexBlocksFromNode + IndexResponse response = indexRequest + .setRefresh(false) + .execute().actionGet(); + + return response.getId(); + } + +} diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java new file mode 100644 index 00000000..282c53be --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java @@ -0,0 +1,36 @@ +package org.duniter.elasticsearch.gchange.service; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.Module; + +public class ServiceModule extends AbstractModule implements Module { + + @Override protected void configure() { + bind(RegistryService.class).asEagerSingleton(); + bind(CitiesRegistryService.class).asEagerSingleton(); + bind(MarketService.class).asEagerSingleton(); + bind(SynchroService.class).asEagerSingleton(); + } +} \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java new file mode 100644 index 00000000..c9156858 --- /dev/null +++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java @@ -0,0 +1,74 @@ +package org.duniter.elasticsearch.gchange.service; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.service.CryptoService; +import org.duniter.elasticsearch.gchange.PluginSettings; +import org.duniter.elasticsearch.service.AbstractSynchroService; +import org.duniter.elasticsearch.service.ServiceLocator; +import org.duniter.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; + +/** + * Created by blavenie on 27/10/16. + */ +public class SynchroService extends AbstractSynchroService { + + @Inject + public SynchroService(Client client, PluginSettings settings, CryptoService cryptoService, + ThreadPool threadPool, final ServiceLocator serviceLocator) { + super(client, settings, cryptoService, threadPool, serviceLocator); + } + + public void synchronize() { + logger.info("Synchronizing data..."); + Peer peer = getPeerFromAPI("GCHANGE API"); + synchronize(peer); + } + + /* -- protected methods -- */ + + protected void synchronize(Peer peer) { + + long sinceTime = 0; // TODO: get last sync time from somewhere ? (e.g. a specific index) + + logger.info(String.format("[%s] Synchronizing gchange data since %s...", peer.toString(), sinceTime)); + + importMarketChanges(peer, sinceTime); + importRegistryChanges(peer, sinceTime); + + logger.info(String.format("[%s] Synchronizing gchange data since %s [OK]", peer.toString(), sinceTime)); + } + + protected void importMarketChanges(Peer peer, long sinceTime) { + importChanges(peer, MarketService.INDEX, MarketService.RECORD_TYPE, sinceTime); + importChanges(peer, MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE, sinceTime); + } + + protected void importRegistryChanges(Peer peer, long sinceTime) { + importChanges(peer, RegistryService.INDEX, RegistryService.RECORD_TYPE, sinceTime); + importChanges(peer, RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE, sinceTime); + } +} diff --git a/duniter4j-elasticsearch/src/main/misc/cities-fr.geoJson.txt b/duniter4j-es-gchange/src/main/misc/cities-fr.geoJson.txt similarity index 100% rename from duniter4j-elasticsearch/src/main/misc/cities-fr.geoJson.txt rename to duniter4j-es-gchange/src/main/misc/cities-fr.geoJson.txt diff --git a/duniter4j-elasticsearch/src/main/misc/index.sh b/duniter4j-es-gchange/src/main/misc/index.sh similarity index 100% rename from duniter4j-elasticsearch/src/main/misc/index.sh rename to duniter4j-es-gchange/src/main/misc/index.sh diff --git a/duniter4j-elasticsearch/src/main/misc/registry-categories-naf2008_liste_n5.ods b/duniter4j-es-gchange/src/main/misc/registry-categories-naf2008_liste_n5.ods similarity index 100% rename from duniter4j-elasticsearch/src/main/misc/registry-categories-naf2008_liste_n5.ods rename to duniter4j-es-gchange/src/main/misc/registry-categories-naf2008_liste_n5.ods diff --git a/duniter4j-es-gchange/src/main/resources/market-categories-bulk-insert.json b/duniter4j-es-gchange/src/main/resources/market-categories-bulk-insert.json new file mode 100644 index 00000000..c9527a96 --- /dev/null +++ b/duniter4j-es-gchange/src/main/resources/market-categories-bulk-insert.json @@ -0,0 +1,151 @@ +{ "index": { "_id": "cat71"}} +{ "name": "Emploi" , "parent": null} +{ "index": { "_id": "cat33"}} +{ "name": "Offres d'emploi", "parent": "cat71" } + +{ "index": { "_id": "cat1" }} +{ "name": "Véhicules" , "parent": null} +{ "index": { "_id": "cat2" }} +{ "name": "Voitures" , "parent": "cat2" } +{ "index": { "_id": "cat3" }} +{ "name": "Motos" , "parent": "cat3" } +{ "index": { "_id": "cat4" }} +{ "name": "Caravaning" , "parent": "cat3" } +{ "index": { "_id": "cat5" }} +{ "name": "Utilitaires" , "parent": "cat3" } +{ "index": { "_id": "cat6" }} +{ "name": "Equipement Auto" , "parent": "cat3" } +{ "index": { "_id": "cat44" }} +{ "name": "Equipement Moto" , "parent": "cat3" } +{ "index": { "_id": "cat50" }} +{ "name": "Equipement Caravaning" , "parent": "cat3" } +{ "index": { "_id": "cat7" }} +{ "name": "Nautisme" , "parent": "cat3" } +{ "index": { "_id": "cat51" }} +{ "name": "Equipement Nautisme" , "parent": "cat3" } + +{ "index": { "_id": "cat8" }} +{ "name": "Immobilier" , "parent": null} +{ "index": { "_id": "cat9" }} +{ "name": "Ventes immobilières" , "parent": "cat8" } +{ "index": { "_id": "cat10" }} +{ "name": "Locations" , "parent": "cat8" } +{ "index": { "_id": "cat11" }} +{ "name": "Colocations" , "parent": "cat8" } +{ "index": { "_id": "cat13" }} +{ "name": "Bureaux & Commerces" , "parent": "cat8" } + +{ "index": { "_id": "cat66" }} +{ "name": "Vacances" , "parent": null} +{ "index": { "_id": "cat12" }} +{ "name": "Locations & Gîtes" , "parent": "cat66" } +{ "index": { "_id": "cat67" }} +{ "name": "Chambres d'hôtes" , "parent": "cat66" } +{ "index": { "_id": "cat68" }} +{ "name": "Campings" , "parent": "cat66" } +{ "index": { "_id": "cat69" }} +{ "name": "Hôtels" , "parent": "cat66" } +{ "index": { "_id": "cat70" }} +{ "name": "Hébergements insolites" , "parent": "cat66" } + +{ "index": { "_id": "cat14" }} +{ "name": "Multimédia" , "parent": null} +{ "index": { "_id": "cat15" }} +{ "name": "Informatique" , "parent": "cat14" } +{ "index": { "_id": "cat43" }} +{ "name": "Consoles & Jeux vidéo" , "parent": "cat14" } +{ "index": { "_id": "cat16" }} +{ "name": "Image & Son" , "parent": "cat14" } +{ "index": { "_id": "cat17" }} +{ "name": "Téléphonie" , "parent": "cat14" } + +{ "index": { "_id": "cat18" }} +{ "name": "Maison" , "parent": null} +{ "index": { "_id": "cat19" }} +{ "name": "Ameublement" , "parent": "cat18" } +{ "index": { "_id": "cat20" }} +{ "name": "Electroménager" , "parent": "cat18" } +{ "index": { "_id": "cat45" }} +{ "name": "Arts de la table" , "parent": "cat18" } +{ "index": { "_id": "cat39" }} +{ "name": "Décoration" , "parent": "cat18" } +{ "index": { "_id": "cat46" }} +{ "name": "Linge de maison" , "parent": "cat18" } +{ "index": { "_id": "cat21" }} +{ "name": "Bricolage" , "parent": "cat18" } +{ "index": { "_id": "cat52" }} +{ "name": "Jardinage" , "parent": "cat18" } +{ "index": { "_id": "cat22" }} +{ "name": "Vêtements" , "parent": "cat18" } +{ "index": { "_id": "cat53" }} +{ "name": "Chaussures" , "parent": "cat18" } +{ "index": { "_id": "cat47" }} +{ "name": "Accessoires & Bagagerie" , "parent": "cat18" } +{ "index": { "_id": "cat42" }} +{ "name": "Montres & Bijoux" , "parent": "cat18" } +{ "index": { "_id": "cat23" }} +{ "name": "Equipement bébé" , "parent": "cat18" } +{ "index": { "_id": "cat54" }} +{ "name": "Vêtements bébé" , "parent": "cat18" } + +{ "index": { "_id": "cat24" }} +{ "name": "Loisirs" , "parent": null} +{ "index": { "_id": "cat25" }} +{ "name": "DVD / Films" , "parent": "cat24" } +{ "index": { "_id": "cat26" }} +{ "name": "CD / Musique" , "parent": "cat24" } +{ "index": { "_id": "cat27" }} +{ "name": "Livres" , "parent": "cat24" } +{ "index": { "_id": "cat28" }} +{ "name": "Animaux" , "parent": "cat24" } +{ "index": { "_id": "cat55" }} +{ "name": "Vélos" , "parent": "cat24" } +{ "index": { "_id": "cat29" }} +{ "name": "Sports & Hobbies" } +{ "index": { "_id": "cat30" }} +{ "name": "Instruments de musique" , "parent": "cat24" } +{ "index": { "_id": "cat40" }} +{ "name": "Collection" , "parent": "cat24" } +{ "index": { "_id": "cat41" }} +{ "name": "Jeux & Jouets" , "parent": "cat24" } +{ "index": { "_id": "cat48" }} +{ "name": "Vins & Gastronomie" , "parent": "cat24" } + +{ "index": { "_id": "cat56" }} +{ "name": "Matériel professionnel" , "parent": null} +{ "index": { "_id": "cat57" }} +{ "name": "Matériel Agricole" , "parent": "cat56" } +{ "index": { "_id": "cat58" }} +{ "name": "Transport - Manutention" , "parent": "cat56" } +{ "index": { "_id": "cat59" }} +{ "name": "BTP - Chantier Gros-oeuvre" , "parent": "cat56" } +{ "index": { "_id": "cat60" }} +{ "name": "Outillage - Matériaux 2nd-oeuvre" , "parent": "cat56" } +{ "index": { "_id": "cat32" }} +{ "name": "Équipements Industriels" , "parent": "cat56" } +{ "index": { "_id": "cat61" }} +{ "name": "Restauration - Hôtellerie" , "parent": "cat56" } +{ "index": { "_id": "cat62" }} +{ "name": "Fournitures de Bureau" , "parent": "cat56" } +{ "index": { "_id": "cat63" }} +{ "name": "Commerces & Marchés" , "parent": "cat56" } +{ "index": { "_id": "cat64" }} +{ "name": "Matériel Médical" , "parent": "cat56" } + +{ "index": { "_id": "cat31" }} +{ "name": "Services" , "parent": null} +{ "index": { "_id": "cat34" }} +{ "name": "Prestations de services" , "parent": "cat31" } +{ "index": { "_id": "cat35" }} +{ "name": "Billetterie" , "parent": "cat31" } +{ "index": { "_id": "cat49" }} +{ "name": "Evénements" , "parent": "cat31" } +{ "index": { "_id": "cat36" }} +{ "name": "Cours particuliers" , "parent": "cat31" } +{ "index": { "_id": "cat65" }} +{ "name": "Covoiturage" , "parent": "cat31" } +{ "index": { "_id": "cat37" }} + +{ "name": "Divers" , "parent": null} +{ "index": { "_id": "cat38" }} +{ "name": "Autres" , "parent": "cat37" } diff --git a/duniter4j-es-gchange/src/main/resources/plugin-security.policy b/duniter4j-es-gchange/src/main/resources/plugin-security.policy new file mode 100644 index 00000000..3ea59400 --- /dev/null +++ b/duniter4j-es-gchange/src/main/resources/plugin-security.policy @@ -0,0 +1,5 @@ +grant codeBase "file:${es.path.home}/plugins/duniter4j-es-gchange/"{ + permission java.io.FilePermission "/etc/ld.so.conf", "read"; + permission java.io.FilePermission "/etc/ld.so.conf.d/*.conf", "read"; + permission java.io.FilePermission "/usr/local/lib/*", "read"; +}; \ No newline at end of file diff --git a/duniter4j-es-gchange/src/main/resources/registry-categories-bulk-insert.json b/duniter4j-es-gchange/src/main/resources/registry-categories-bulk-insert.json new file mode 100644 index 00000000..3ee70de3 --- /dev/null +++ b/duniter4j-es-gchange/src/main/resources/registry-categories-bulk-insert.json @@ -0,0 +1,1506 @@ +{"index": {"_id": "A"}} +{"name": "Agriculture, sylviculture et pêche", "parent": null} +{"index": {"_id": "01.11Z"}} +{"name": "Culture de céréales (à l'exception du riz), de légumineuses et de graines oléagineuses", "parent": "A"} +{"index": {"_id": "01.12Z"}} +{"name": "Culture du riz", "parent": "A"} +{"index": {"_id": "01.13Z"}} +{"name": "Culture de légumes, de melons, de racines et de tubercules", "parent": "A"} +{"index": {"_id": "01.14Z"}} +{"name": "Culture de la canne à sucre", "parent": "A"} +{"index": {"_id": "01.15Z"}} +{"name": "Culture du tabac", "parent": "A"} +{"index": {"_id": "01.16Z"}} +{"name": "Culture de plantes à fibres", "parent": "A"} +{"index": {"_id": "01.19Z"}} +{"name": "Autres cultures non permanentes", "parent": "A"} +{"index": {"_id": "01.21Z"}} +{"name": "Culture de la vigne", "parent": "A"} +{"index": {"_id": "01.22Z"}} +{"name": "Culture de fruits tropicaux et subtropicaux", "parent": "A"} +{"index": {"_id": "01.23Z"}} +{"name": "Culture d'agrumes", "parent": "A"} +{"index": {"_id": "01.24Z"}} +{"name": "Culture de fruits à pépins et à noyau", "parent": "A"} +{"index": {"_id": "01.25Z"}} +{"name": "Culture d'autres fruits d'arbres ou d'arbustes et de fruits à coque", "parent": "A"} +{"index": {"_id": "01.26Z"}} +{"name": "Culture de fruits oléagineux", "parent": "A"} +{"index": {"_id": "01.27Z"}} +{"name": "Culture de plantes à boissons", "parent": "A"} +{"index": {"_id": "01.28Z"}} +{"name": "Culture de plantes à épices, aromatiques, médicinales et pharmaceutiques", "parent": "A"} +{"index": {"_id": "01.29Z"}} +{"name": "Autres cultures permanentes", "parent": "A"} +{"index": {"_id": "01.30Z"}} +{"name": "Reproduction de plantes", "parent": "A"} +{"index": {"_id": "01.41Z"}} +{"name": "Élevage de vaches laitières", "parent": "A"} +{"index": {"_id": "01.42Z"}} +{"name": "Élevage d'autres bovins et de buffles", "parent": "A"} +{"index": {"_id": "01.43Z"}} +{"name": "Élevage de chevaux et d'autres équidés", "parent": "A"} +{"index": {"_id": "01.44Z"}} +{"name": "Élevage de chameaux et d'autres camélidés", "parent": "A"} +{"index": {"_id": "01.45Z"}} +{"name": "Élevage d'ovins et de caprins", "parent": "A"} +{"index": {"_id": "01.46Z"}} +{"name": "Élevage de porcins", "parent": "A"} +{"index": {"_id": "01.47Z"}} +{"name": "Élevage de volailles", "parent": "A"} +{"index": {"_id": "01.49Z"}} +{"name": "Élevage d'autres animaux", "parent": "A"} +{"index": {"_id": "01.50Z"}} +{"name": "Culture et élevage associés", "parent": "A"} +{"index": {"_id": "01.61Z"}} +{"name": "Activités de soutien aux cultures", "parent": "A"} +{"index": {"_id": "01.62Z"}} +{"name": "Activités de soutien à la production animale", "parent": "A"} +{"index": {"_id": "01.63Z"}} +{"name": "Traitement primaire des récoltes", "parent": "A"} +{"index": {"_id": "01.64Z"}} +{"name": "Traitement des semences", "parent": "A"} +{"index": {"_id": "01.70Z"}} +{"name": "Chasse, piégeage et services annexes", "parent": "A"} +{"index": {"_id": "02.10Z"}} +{"name": "Sylviculture et autres activités forestières", "parent": "A"} +{"index": {"_id": "02.20Z"}} +{"name": "Exploitation forestière", "parent": "A"} +{"index": {"_id": "02.30Z"}} +{"name": "Récolte de produits forestiers non ligneux poussant à l'état sauvage", "parent": "A"} +{"index": {"_id": "02.40Z"}} +{"name": "Services de soutien à l'exploitation forestière", "parent": "A"} +{"index": {"_id": "03.11Z"}} +{"name": "Pêche en mer", "parent": "A"} +{"index": {"_id": "03.12Z"}} +{"name": "Pêche en eau douce", "parent": "A"} +{"index": {"_id": "03.21Z"}} +{"name": "Aquaculture en mer", "parent": "A"} +{"index": {"_id": "03.22Z"}} +{"name": "Aquaculture en eau douce", "parent": "A"} +{"index": {"_id": "B"}} +{"name": "Industries extractives", "parent": null} +{"index": {"_id": "05.10Z"}} +{"name": "Extraction de houille", "parent": "B"} +{"index": {"_id": "05.20Z"}} +{"name": "Extraction de lignite", "parent": "B"} +{"index": {"_id": "06.10Z"}} +{"name": "Extraction de pétrole brut", "parent": "B"} +{"index": {"_id": "06.20Z"}} +{"name": "Extraction de gaz naturel", "parent": "B"} +{"index": {"_id": "07.10Z"}} +{"name": "Extraction de minerais de fer", "parent": "B"} +{"index": {"_id": "07.21Z"}} +{"name": "Extraction de minerais d'uranium et de thorium", "parent": "B"} +{"index": {"_id": "07.29Z"}} +{"name": "Extraction d'autres minerais de métaux non ferreux", "parent": "B"} +{"index": {"_id": "08.11Z"}} +{"name": "Extraction de pierres ornementales et de construction, de calcaire industriel, de gypse, de craie et d'ardoise", "parent": "B"} +{"index": {"_id": "08.12Z"}} +{"name": "Exploitation de gravières et sablières, extraction d'argiles et de kaolin", "parent": "B"} +{"index": {"_id": "08.91Z"}} +{"name": "Extraction des minéraux chimiques et d'engrais minéraux", "parent": "B"} +{"index": {"_id": "08.92Z"}} +{"name": "Extraction de tourbe", "parent": "B"} +{"index": {"_id": "08.93Z"}} +{"name": "Production de sel", "parent": "B"} +{"index": {"_id": "08.99Z"}} +{"name": "Autres activités extractives n.c.a.", "parent": "B"} +{"index": {"_id": "09.10Z"}} +{"name": "Activités de soutien à l'extraction d'hydrocarbures", "parent": "B"} +{"index": {"_id": "09.90Z"}} +{"name": "Activités de soutien aux autres industries extractives", "parent": "B"} +{"index": {"_id": "C"}} +{"name": "Industrie manufacturière", "parent": null} +{"index": {"_id": "10.11Z"}} +{"name": "Transformation et conservation de la viande de boucherie", "parent": "C"} +{"index": {"_id": "10.12Z"}} +{"name": "Transformation et conservation de la viande de volaille", "parent": "C"} +{"index": {"_id": "10.13A"}} +{"name": "Préparation industrielle de produits à base de viande", "parent": "C"} +{"index": {"_id": "10.13B"}} +{"name": "Charcuterie", "parent": "C"} +{"index": {"_id": "10.20Z"}} +{"name": "Transformation et conservation de poisson, de crustacés et de mollusques", "parent": "C"} +{"index": {"_id": "10.31Z"}} +{"name": "Transformation et conservation de pommes de terre", "parent": "C"} +{"index": {"_id": "10.32Z"}} +{"name": "Préparation de jus de fruits et légumes", "parent": "C"} +{"index": {"_id": "10.39A"}} +{"name": "Autre transformation et conservation de légumes", "parent": "C"} +{"index": {"_id": "10.39B"}} +{"name": "Transformation et conservation de fruits", "parent": "C"} +{"index": {"_id": "10.41A"}} +{"name": "Fabrication d'huiles et graisses brutes", "parent": "C"} +{"index": {"_id": "10.41B"}} +{"name": "Fabrication d'huiles et graisses raffinées", "parent": "C"} +{"index": {"_id": "10.42Z"}} +{"name": "Fabrication de margarine et graisses comestibles similaires", "parent": "C"} +{"index": {"_id": "10.51A"}} +{"name": "Fabrication de lait liquide et de produits frais", "parent": "C"} +{"index": {"_id": "10.51B"}} +{"name": "Fabrication de beurre", "parent": "C"} +{"index": {"_id": "10.51C"}} +{"name": "Fabrication de fromage", "parent": "C"} +{"index": {"_id": "10.51D"}} +{"name": "Fabrication d'autres produits laitiers", "parent": "C"} +{"index": {"_id": "10.52Z"}} +{"name": "Fabrication de glaces et sorbets", "parent": "C"} +{"index": {"_id": "10.61A"}} +{"name": "Meunerie", "parent": "C"} +{"index": {"_id": "10.61B"}} +{"name": "Autres activités du travail des grains", "parent": "C"} +{"index": {"_id": "10.62Z"}} +{"name": "Fabrication de produits amylacés", "parent": "C"} +{"index": {"_id": "10.71A"}} +{"name": "Fabrication industrielle de pain et de pâtisserie fraîche", "parent": "C"} +{"index": {"_id": "10.71B"}} +{"name": "Cuisson de produits de boulangerie", "parent": "C"} +{"index": {"_id": "10.71C"}} +{"name": "Boulangerie et boulangerie-pâtisserie", "parent": "C"} +{"index": {"_id": "10.71D"}} +{"name": "Pâtisserie", "parent": "C"} +{"index": {"_id": "10.72Z"}} +{"name": "Fabrication de biscuits, biscottes et pâtisseries de conservation", "parent": "C"} +{"index": {"_id": "10.73Z"}} +{"name": "Fabrication de pâtes alimentaires", "parent": "C"} +{"index": {"_id": "10.81Z"}} +{"name": "Fabrication de sucre", "parent": "C"} +{"index": {"_id": "10.82Z"}} +{"name": "Fabrication de cacao, chocolat et de produits de confiserie", "parent": "C"} +{"index": {"_id": "10.83Z"}} +{"name": "Transformation du thé et du café", "parent": "C"} +{"index": {"_id": "10.84Z"}} +{"name": "Fabrication de condiments et assaisonnements", "parent": "C"} +{"index": {"_id": "10.85Z"}} +{"name": "Fabrication de plats préparés", "parent": "C"} +{"index": {"_id": "10.86Z"}} +{"name": "Fabrication d'aliments homogénéisés et diététiques", "parent": "C"} +{"index": {"_id": "10.89Z"}} +{"name": "Fabrication d'autres produits alimentaires n.c.a.", "parent": "C"} +{"index": {"_id": "10.91Z"}} +{"name": "Fabrication d'aliments pour animaux de ferme", "parent": "C"} +{"index": {"_id": "10.92Z"}} +{"name": "Fabrication d'aliments pour animaux de compagnie", "parent": "C"} +{"index": {"_id": "11.01Z"}} +{"name": "Production de boissons alcooliques distillées", "parent": "C"} +{"index": {"_id": "11.02A"}} +{"name": "Fabrication de vins effervescents", "parent": "C"} +{"index": {"_id": "11.02B"}} +{"name": "Vinification", "parent": "C"} +{"index": {"_id": "11.03Z"}} +{"name": "Fabrication de cidre et de vins de fruits", "parent": "C"} +{"index": {"_id": "11.04Z"}} +{"name": "Production d'autres boissons fermentées non distillées", "parent": "C"} +{"index": {"_id": "11.05Z"}} +{"name": "Fabrication de bière", "parent": "C"} +{"index": {"_id": "11.06Z"}} +{"name": "Fabrication de malt", "parent": "C"} +{"index": {"_id": "11.07A"}} +{"name": "Industrie des eaux de table", "parent": "C"} +{"index": {"_id": "11.07B"}} +{"name": "Production de boissons rafraîchissantes", "parent": "C"} +{"index": {"_id": "12.00Z"}} +{"name": "Fabrication de produits à base de tabac", "parent": "C"} +{"index": {"_id": "13.10Z"}} +{"name": "Préparation de fibres textiles et filature", "parent": "C"} +{"index": {"_id": "13.20Z"}} +{"name": "Tissage", "parent": "C"} +{"index": {"_id": "13.30Z"}} +{"name": "Ennoblissement textile", "parent": "C"} +{"index": {"_id": "13.91Z"}} +{"name": "Fabrication d'étoffes à mailles", "parent": "C"} +{"index": {"_id": "13.92Z"}} +{"name": "Fabrication d'articles textiles, sauf habillement", "parent": "C"} +{"index": {"_id": "13.93Z"}} +{"name": "Fabrication de tapis et moquettes", "parent": "C"} +{"index": {"_id": "13.94Z"}} +{"name": "Fabrication de ficelles, cordes et filets", "parent": "C"} +{"index": {"_id": "13.95Z"}} +{"name": "Fabrication de non-tissés, sauf habillement", "parent": "C"} +{"index": {"_id": "13.96Z"}} +{"name": "Fabrication d'autres textiles techniques et industriels", "parent": "C"} +{"index": {"_id": "13.99Z"}} +{"name": "Fabrication d'autres textiles n.c.a.", "parent": "C"} +{"index": {"_id": "14.11Z"}} +{"name": "Fabrication de vêtements en cuir", "parent": "C"} +{"index": {"_id": "14.12Z"}} +{"name": "Fabrication de vêtements de travail", "parent": "C"} +{"index": {"_id": "14.13Z"}} +{"name": "Fabrication de vêtements de dessus", "parent": "C"} +{"index": {"_id": "14.14Z"}} +{"name": "Fabrication de vêtements de dessous", "parent": "C"} +{"index": {"_id": "14.19Z"}} +{"name": "Fabrication d'autres vêtements et accessoires", "parent": "C"} +{"index": {"_id": "14.20Z"}} +{"name": "Fabrication d'articles en fourrure", "parent": "C"} +{"index": {"_id": "14.31Z"}} +{"name": "Fabrication d'articles chaussants à mailles", "parent": "C"} +{"index": {"_id": "14.39Z"}} +{"name": "Fabrication d'autres articles à mailles", "parent": "C"} +{"index": {"_id": "15.11Z"}} +{"name": "Apprêt et tannage des cuirs ; préparation et teinture des fourrures", "parent": "C"} +{"index": {"_id": "15.12Z"}} +{"name": "Fabrication d'articles de voyage, de maroquinerie et de sellerie", "parent": "C"} +{"index": {"_id": "15.20Z"}} +{"name": "Fabrication de chaussures", "parent": "C"} +{"index": {"_id": "16.10A"}} +{"name": "Sciage et rabotage du bois, hors imprégnation", "parent": "C"} +{"index": {"_id": "16.10B"}} +{"name": "Imprégnation du bois", "parent": "C"} +{"index": {"_id": "16.21Z"}} +{"name": "Fabrication de placage et de panneaux de bois", "parent": "C"} +{"index": {"_id": "16.22Z"}} +{"name": "Fabrication de parquets assemblés", "parent": "C"} +{"index": {"_id": "16.23Z"}} +{"name": "Fabrication de charpentes et d'autres menuiseries", "parent": "C"} +{"index": {"_id": "16.24Z"}} +{"name": "Fabrication d'emballages en bois", "parent": "C"} +{"index": {"_id": "16.29Z"}} +{"name": "Fabrication d'objets divers en bois ; fabrication d'objets en liège, vannerie et sparterie", "parent": "C"} +{"index": {"_id": "17.11Z"}} +{"name": "Fabrication de pâte à papier", "parent": "C"} +{"index": {"_id": "17.12Z"}} +{"name": "Fabrication de papier et de carton", "parent": "C"} +{"index": {"_id": "17.21A"}} +{"name": "Fabrication de carton ondulé", "parent": "C"} +{"index": {"_id": "17.21B"}} +{"name": "Fabrication de cartonnages", "parent": "C"} +{"index": {"_id": "17.21C"}} +{"name": "Fabrication d'emballages en papier", "parent": "C"} +{"index": {"_id": "17.22Z"}} +{"name": "Fabrication d'articles en papier à usage sanitaire ou domestique", "parent": "C"} +{"index": {"_id": "17.23Z"}} +{"name": "Fabrication d'articles de papeterie", "parent": "C"} +{"index": {"_id": "17.24Z"}} +{"name": "Fabrication de papiers peints", "parent": "C"} +{"index": {"_id": "17.29Z"}} +{"name": "Fabrication d'autres articles en papier ou en carton", "parent": "C"} +{"index": {"_id": "18.11Z"}} +{"name": "Imprimerie de journaux", "parent": "C"} +{"index": {"_id": "18.12Z"}} +{"name": "Autre imprimerie (labeur)", "parent": "C"} +{"index": {"_id": "18.13Z"}} +{"name": "Activités de pré-presse", "parent": "C"} +{"index": {"_id": "18.14Z"}} +{"name": "Reliure et activités connexes", "parent": "C"} +{"index": {"_id": "18.20Z"}} +{"name": "Reproduction d'enregistrements", "parent": "C"} +{"index": {"_id": "19.10Z"}} +{"name": "Cokéfaction", "parent": "C"} +{"index": {"_id": "19.20Z"}} +{"name": "Raffinage du pétrole", "parent": "C"} +{"index": {"_id": "20.11Z"}} +{"name": "Fabrication de gaz industriels", "parent": "C"} +{"index": {"_id": "20.12Z"}} +{"name": "Fabrication de colorants et de pigments", "parent": "C"} +{"index": {"_id": "20.13A"}} +{"name": "Enrichissement et retraitement de matières nucléaires", "parent": "C"} +{"index": {"_id": "20.13B"}} +{"name": "Fabrication d'autres produits chimiques inorganiques de base n.c.a.", "parent": "C"} +{"index": {"_id": "20.14Z"}} +{"name": "Fabrication d'autres produits chimiques organiques de base", "parent": "C"} +{"index": {"_id": "20.15Z"}} +{"name": "Fabrication de produits azotés et d'engrais", "parent": "C"} +{"index": {"_id": "20.16Z"}} +{"name": "Fabrication de matières plastiques de base", "parent": "C"} +{"index": {"_id": "20.17Z"}} +{"name": "Fabrication de caoutchouc synthétique", "parent": "C"} +{"index": {"_id": "20.20Z"}} +{"name": "Fabrication de pesticides et d'autres produits agrochimiques", "parent": "C"} +{"index": {"_id": "20.30Z"}} +{"name": "Fabrication de peintures, vernis, encres et mastics", "parent": "C"} +{"index": {"_id": "20.41Z"}} +{"name": "Fabrication de savons, détergents et produits d'entretien", "parent": "C"} +{"index": {"_id": "20.42Z"}} +{"name": "Fabrication de parfums et de produits pour la toilette", "parent": "C"} +{"index": {"_id": "20.51Z"}} +{"name": "Fabrication de produits explosifs", "parent": "C"} +{"index": {"_id": "20.52Z"}} +{"name": "Fabrication de colles", "parent": "C"} +{"index": {"_id": "20.53Z"}} +{"name": "Fabrication d'huiles essentielles", "parent": "C"} +{"index": {"_id": "20.59Z"}} +{"name": "Fabrication d'autres produits chimiques n.c.a.", "parent": "C"} +{"index": {"_id": "20.60Z"}} +{"name": "Fabrication de fibres artificielles ou synthétiques", "parent": "C"} +{"index": {"_id": "21.10Z"}} +{"name": "Fabrication de produits pharmaceutiques de base", "parent": "C"} +{"index": {"_id": "21.20Z"}} +{"name": "Fabrication de préparations pharmaceutiques", "parent": "C"} +{"index": {"_id": "22.11Z"}} +{"name": "Fabrication et rechapage de pneumatiques", "parent": "C"} +{"index": {"_id": "22.19Z"}} +{"name": "Fabrication d'autres articles en caoutchouc", "parent": "C"} +{"index": {"_id": "22.21Z"}} +{"name": "Fabrication de plaques, feuilles, tubes et profilés en matières plastiques", "parent": "C"} +{"index": {"_id": "22.22Z"}} +{"name": "Fabrication d'emballages en matières plastiques", "parent": "C"} +{"index": {"_id": "22.23Z"}} +{"name": "Fabrication d'éléments en matières plastiques pour la construction", "parent": "C"} +{"index": {"_id": "22.29A"}} +{"name": "Fabrication de pièces techniques à base de matières plastiques", "parent": "C"} +{"index": {"_id": "22.29B"}} +{"name": "Fabrication de produits de consommation courante en matières plastiques", "parent": "C"} +{"index": {"_id": "23.11Z"}} +{"name": "Fabrication de verre plat", "parent": "C"} +{"index": {"_id": "23.12Z"}} +{"name": "Façonnage et transformation du verre plat", "parent": "C"} +{"index": {"_id": "23.13Z"}} +{"name": "Fabrication de verre creux", "parent": "C"} +{"index": {"_id": "23.14Z"}} +{"name": "Fabrication de fibres de verre", "parent": "C"} +{"index": {"_id": "23.19Z"}} +{"name": "Fabrication et façonnage d'autres articles en verre, y compris verre technique", "parent": "C"} +{"index": {"_id": "23.20Z"}} +{"name": "Fabrication de produits réfractaires", "parent": "C"} +{"index": {"_id": "23.31Z"}} +{"name": "Fabrication de carreaux en céramique", "parent": "C"} +{"index": {"_id": "23.32Z"}} +{"name": "Fabrication de briques, tuiles et produits de construction, en terre cuite", "parent": "C"} +{"index": {"_id": "23.41Z"}} +{"name": "Fabrication d'articles céramiques à usage domestique ou ornemental", "parent": "C"} +{"index": {"_id": "23.42Z"}} +{"name": "Fabrication d'appareils sanitaires en céramique", "parent": "C"} +{"index": {"_id": "23.43Z"}} +{"name": "Fabrication d'isolateurs et pièces isolantes en céramique", "parent": "C"} +{"index": {"_id": "23.44Z"}} +{"name": "Fabrication d'autres produits céramiques à usage technique", "parent": "C"} +{"index": {"_id": "23.49Z"}} +{"name": "Fabrication d'autres produits céramiques", "parent": "C"} +{"index": {"_id": "23.51Z"}} +{"name": "Fabrication de ciment", "parent": "C"} +{"index": {"_id": "23.52Z"}} +{"name": "Fabrication de chaux et plâtre", "parent": "C"} +{"index": {"_id": "23.61Z"}} +{"name": "Fabrication d'éléments en béton pour la construction", "parent": "C"} +{"index": {"_id": "23.62Z"}} +{"name": "Fabrication d'éléments en plâtre pour la construction", "parent": "C"} +{"index": {"_id": "23.63Z"}} +{"name": "Fabrication de béton prêt à l'emploi", "parent": "C"} +{"index": {"_id": "23.64Z"}} +{"name": "Fabrication de mortiers et bétons secs", "parent": "C"} +{"index": {"_id": "23.65Z"}} +{"name": "Fabrication d'ouvrages en fibre-ciment", "parent": "C"} +{"index": {"_id": "23.69Z"}} +{"name": "Fabrication d'autres ouvrages en béton, en ciment ou en plâtre", "parent": "C"} +{"index": {"_id": "23.70Z"}} +{"name": "Taille, façonnage et finissage de pierres", "parent": "C"} +{"index": {"_id": "23.91Z"}} +{"name": "Fabrication de produits abrasifs", "parent": "C"} +{"index": {"_id": "23.99Z"}} +{"name": "Fabrication d'autres produits minéraux non métalliques n.c.a.", "parent": "C"} +{"index": {"_id": "24.10Z"}} +{"name": "Sidérurgie", "parent": "C"} +{"index": {"_id": "24.20Z"}} +{"name": "Fabrication de tubes, tuyaux, profilés creux et accessoires correspondants en acier", "parent": "C"} +{"index": {"_id": "24.31Z"}} +{"name": "Étirage à froid de barres", "parent": "C"} +{"index": {"_id": "24.32Z"}} +{"name": "Laminage à froid de feuillards", "parent": "C"} +{"index": {"_id": "24.33Z"}} +{"name": "Profilage à froid par formage ou pliage", "parent": "C"} +{"index": {"_id": "24.34Z"}} +{"name": "Tréfilage à froid", "parent": "C"} +{"index": {"_id": "24.41Z"}} +{"name": "Production de métaux précieux", "parent": "C"} +{"index": {"_id": "24.42Z"}} +{"name": "Métallurgie de l'aluminium", "parent": "C"} +{"index": {"_id": "24.43Z"}} +{"name": "Métallurgie du plomb, du zinc ou de l'étain", "parent": "C"} +{"index": {"_id": "24.44Z"}} +{"name": "Métallurgie du cuivre", "parent": "C"} +{"index": {"_id": "24.45Z"}} +{"name": "Métallurgie des autres métaux non ferreux", "parent": "C"} +{"index": {"_id": "24.46Z"}} +{"name": "Élaboration et transformation de matières nucléaires", "parent": "C"} +{"index": {"_id": "24.51Z"}} +{"name": "Fonderie de fonte", "parent": "C"} +{"index": {"_id": "24.52Z"}} +{"name": "Fonderie d'acier", "parent": "C"} +{"index": {"_id": "24.53Z"}} +{"name": "Fonderie de métaux légers", "parent": "C"} +{"index": {"_id": "24.54Z"}} +{"name": "Fonderie d'autres métaux non ferreux", "parent": "C"} +{"index": {"_id": "25.11Z"}} +{"name": "Fabrication de structures métalliques et de parties de structures", "parent": "C"} +{"index": {"_id": "25.12Z"}} +{"name": "Fabrication de portes et fenêtres en métal", "parent": "C"} +{"index": {"_id": "25.21Z"}} +{"name": "Fabrication de radiateurs et de chaudières pour le chauffage central", "parent": "C"} +{"index": {"_id": "25.29Z"}} +{"name": "Fabrication d'autres réservoirs, citernes et conteneurs métalliques", "parent": "C"} +{"index": {"_id": "25.30Z"}} +{"name": "Fabrication de générateurs de vapeur, à l'exception des chaudières pour le chauffage central", "parent": "C"} +{"index": {"_id": "25.40Z"}} +{"name": "Fabrication d'armes et de munitions", "parent": "C"} +{"index": {"_id": "25.50A"}} +{"name": "Forge, estampage, matriçage ; métallurgie des poudres", "parent": "C"} +{"index": {"_id": "25.50B"}} +{"name": "Découpage, emboutissage", "parent": "C"} +{"index": {"_id": "25.61Z"}} +{"name": "Traitement et revêtement des métaux", "parent": "C"} +{"index": {"_id": "25.62A"}} +{"name": "Décolletage", "parent": "C"} +{"index": {"_id": "25.62B"}} +{"name": "Mécanique industrielle", "parent": "C"} +{"index": {"_id": "25.71Z"}} +{"name": "Fabrication de coutellerie", "parent": "C"} +{"index": {"_id": "25.72Z"}} +{"name": "Fabrication de serrures et de ferrures", "parent": "C"} +{"index": {"_id": "25.73A"}} +{"name": "Fabrication de moules et modèles", "parent": "C"} +{"index": {"_id": "25.73B"}} +{"name": "Fabrication d'autres outillages", "parent": "C"} +{"index": {"_id": "25.91Z"}} +{"name": "Fabrication de fûts et emballages métalliques similaires", "parent": "C"} +{"index": {"_id": "25.92Z"}} +{"name": "Fabrication d'emballages métalliques légers", "parent": "C"} +{"index": {"_id": "25.93Z"}} +{"name": "Fabrication d'articles en fils métalliques, de chaînes et de ressorts", "parent": "C"} +{"index": {"_id": "25.94Z"}} +{"name": "Fabrication de vis et de boulons", "parent": "C"} +{"index": {"_id": "25.99A"}} +{"name": "Fabrication d'articles métalliques ménagers", "parent": "C"} +{"index": {"_id": "25.99B"}} +{"name": "Fabrication d'autres articles métalliques", "parent": "C"} +{"index": {"_id": "26.11Z"}} +{"name": "Fabrication de composants électroniques", "parent": "C"} +{"index": {"_id": "26.12Z"}} +{"name": "Fabrication de cartes électroniques assemblées", "parent": "C"} +{"index": {"_id": "26.20Z"}} +{"name": "Fabrication d'ordinateurs et d'équipements périphériques", "parent": "C"} +{"index": {"_id": "26.30Z"}} +{"name": "Fabrication d'équipements de communication", "parent": "C"} +{"index": {"_id": "26.40Z"}} +{"name": "Fabrication de produits électroniques grand public", "parent": "C"} +{"index": {"_id": "26.51A"}} +{"name": "Fabrication d'équipements d'aide à la navigation", "parent": "C"} +{"index": {"_id": "26.51B"}} +{"name": "Fabrication d'instrumentation scientifique et technique", "parent": "C"} +{"index": {"_id": "26.52Z"}} +{"name": "Horlogerie", "parent": "C"} +{"index": {"_id": "26.60Z"}} +{"name": "Fabrication d'équipements d'irradiation médicale, d'équipements électromédicaux et électrothérapeutiques", "parent": "C"} +{"index": {"_id": "26.70Z"}} +{"name": "Fabrication de matériels optique et photographique", "parent": "C"} +{"index": {"_id": "26.80Z"}} +{"name": "Fabrication de supports magnétiques et optiques", "parent": "C"} +{"index": {"_id": "27.11Z"}} +{"name": "Fabrication de moteurs, génératrices et transformateurs électriques", "parent": "C"} +{"index": {"_id": "27.12Z"}} +{"name": "Fabrication de matériel de distribution et de commande électrique", "parent": "C"} +{"index": {"_id": "27.20Z"}} +{"name": "Fabrication de piles et d'accumulateurs électriques", "parent": "C"} +{"index": {"_id": "27.31Z"}} +{"name": "Fabrication de câbles de fibres optiques", "parent": "C"} +{"index": {"_id": "27.32Z"}} +{"name": "Fabrication d'autres fils et câbles électroniques ou électriques", "parent": "C"} +{"index": {"_id": "27.33Z"}} +{"name": "Fabrication de matériel d'installation électrique", "parent": "C"} +{"index": {"_id": "27.40Z"}} +{"name": "Fabrication d'appareils d'éclairage électrique", "parent": "C"} +{"index": {"_id": "27.51Z"}} +{"name": "Fabrication d'appareils électroménagers", "parent": "C"} +{"index": {"_id": "27.52Z"}} +{"name": "Fabrication d'appareils ménagers non électriques", "parent": "C"} +{"index": {"_id": "27.90Z"}} +{"name": "Fabrication d'autres matériels électriques", "parent": "C"} +{"index": {"_id": "28.11Z"}} +{"name": "Fabrication de moteurs et turbines, à l'exception des moteurs d'avions et de véhicules", "parent": "C"} +{"index": {"_id": "28.12Z"}} +{"name": "Fabrication d'équipements hydrauliques et pneumatiques", "parent": "C"} +{"index": {"_id": "28.13Z"}} +{"name": "Fabrication d'autres pompes et compresseurs", "parent": "C"} +{"index": {"_id": "28.14Z"}} +{"name": "Fabrication d'autres articles de robinetterie", "parent": "C"} +{"index": {"_id": "28.15Z"}} +{"name": "Fabrication d'engrenages et d'organes mécaniques de transmission", "parent": "C"} +{"index": {"_id": "28.21Z"}} +{"name": "Fabrication de fours et brûleurs", "parent": "C"} +{"index": {"_id": "28.22Z"}} +{"name": "Fabrication de matériel de levage et de manutention", "parent": "C"} +{"index": {"_id": "28.23Z"}} +{"name": "Fabrication de machines et d'équipements de bureau (à l'exception des ordinateurs et équipements périphériques)", "parent": "C"} +{"index": {"_id": "28.24Z"}} +{"name": "Fabrication d'outillage portatif à moteur incorporé", "parent": "C"} +{"index": {"_id": "28.25Z"}} +{"name": "Fabrication d'équipements aérauliques et frigorifiques industriels", "parent": "C"} +{"index": {"_id": "28.29A"}} +{"name": "Fabrication d'équipements d'emballage, de conditionnement et de pesage", "parent": "C"} +{"index": {"_id": "28.29B"}} +{"name": "Fabrication d'autres machines d'usage général", "parent": "C"} +{"index": {"_id": "28.30Z"}} +{"name": "Fabrication de machines agricoles et forestières", "parent": "C"} +{"index": {"_id": "28.41Z"}} +{"name": "Fabrication de machines-outils pour le travail des métaux", "parent": "C"} +{"index": {"_id": "28.49Z"}} +{"name": "Fabrication d'autres machines-outils", "parent": "C"} +{"index": {"_id": "28.91Z"}} +{"name": "Fabrication de machines pour la métallurgie", "parent": "C"} +{"index": {"_id": "28.92Z"}} +{"name": "Fabrication de machines pour l'extraction ou la construction", "parent": "C"} +{"index": {"_id": "28.93Z"}} +{"name": "Fabrication de machines pour l'industrie agro-alimentaire", "parent": "C"} +{"index": {"_id": "28.94Z"}} +{"name": "Fabrication de machines pour les industries textiles", "parent": "C"} +{"index": {"_id": "28.95Z"}} +{"name": "Fabrication de machines pour les industries du papier et du carton", "parent": "C"} +{"index": {"_id": "28.96Z"}} +{"name": "Fabrication de machines pour le travail du caoutchouc ou des plastiques", "parent": "C"} +{"index": {"_id": "28.99A"}} +{"name": "Fabrication de machines d'imprimerie", "parent": "C"} +{"index": {"_id": "28.99B"}} +{"name": "Fabrication d'autres machines spécialisées", "parent": "C"} +{"index": {"_id": "29.10Z"}} +{"name": "Construction de véhicules automobiles", "parent": "C"} +{"index": {"_id": "29.20Z"}} +{"name": "Fabrication de carrosseries et remorques", "parent": "C"} +{"index": {"_id": "29.31Z"}} +{"name": "Fabrication d'équipements électriques et électroniques automobiles", "parent": "C"} +{"index": {"_id": "29.32Z"}} +{"name": "Fabrication d'autres équipements automobiles", "parent": "C"} +{"index": {"_id": "30.11Z"}} +{"name": "Construction de navires et de structures flottantes", "parent": "C"} +{"index": {"_id": "30.12Z"}} +{"name": "Construction de bateaux de plaisance", "parent": "C"} +{"index": {"_id": "30.20Z"}} +{"name": "Construction de locomotives et d'autre matériel ferroviaire roulant", "parent": "C"} +{"index": {"_id": "30.30Z"}} +{"name": "Construction aéronautique et spatiale", "parent": "C"} +{"index": {"_id": "30.40Z"}} +{"name": "Construction de véhicules militaires de combat", "parent": "C"} +{"index": {"_id": "30.91Z"}} +{"name": "Fabrication de motocycles", "parent": "C"} +{"index": {"_id": "30.92Z"}} +{"name": "Fabrication de bicyclettes et de véhicules pour invalides", "parent": "C"} +{"index": {"_id": "30.99Z"}} +{"name": "Fabrication d'autres équipements de transport n.c.a.", "parent": "C"} +{"index": {"_id": "31.01Z"}} +{"name": "Fabrication de meubles de bureau et de magasin", "parent": "C"} +{"index": {"_id": "31.02Z"}} +{"name": "Fabrication de meubles de cuisine", "parent": "C"} +{"index": {"_id": "31.03Z"}} +{"name": "Fabrication de matelas", "parent": "C"} +{"index": {"_id": "31.09A"}} +{"name": "Fabrication de sièges d'ameublement d'intérieur", "parent": "C"} +{"index": {"_id": "31.09B"}} +{"name": "Fabrication d'autres meubles et industries connexes de l'ameublement", "parent": "C"} +{"index": {"_id": "32.11Z"}} +{"name": "Frappe de monnaie", "parent": "C"} +{"index": {"_id": "32.12Z"}} +{"name": "Fabrication d'articles de joaillerie et bijouterie", "parent": "C"} +{"index": {"_id": "32.13Z"}} +{"name": "Fabrication d'articles de bijouterie fantaisie et articles similaires", "parent": "C"} +{"index": {"_id": "32.20Z"}} +{"name": "Fabrication d'instruments de musique", "parent": "C"} +{"index": {"_id": "32.30Z"}} +{"name": "Fabrication d'articles de sport", "parent": "C"} +{"index": {"_id": "32.40Z"}} +{"name": "Fabrication de jeux et jouets", "parent": "C"} +{"index": {"_id": "32.50A"}} +{"name": "Fabrication de matériel médico-chirurgical et dentaire", "parent": "C"} +{"index": {"_id": "32.50B"}} +{"name": "Fabrication de lunettes", "parent": "C"} +{"index": {"_id": "32.91Z"}} +{"name": "Fabrication d'articles de brosserie", "parent": "C"} +{"index": {"_id": "32.99Z"}} +{"name": "Autres activités manufacturières n.c.a.", "parent": "C"} +{"index": {"_id": "33.11Z"}} +{"name": "Réparation d'ouvrages en métaux", "parent": "C"} +{"index": {"_id": "33.12Z"}} +{"name": "Réparation de machines et équipements mécaniques", "parent": "C"} +{"index": {"_id": "33.13Z"}} +{"name": "Réparation de matériels électroniques et optiques", "parent": "C"} +{"index": {"_id": "33.14Z"}} +{"name": "Réparation d'équipements électriques", "parent": "C"} +{"index": {"_id": "33.15Z"}} +{"name": "Réparation et maintenance navale", "parent": "C"} +{"index": {"_id": "33.16Z"}} +{"name": "Réparation et maintenance d'aéronefs et d'engins spatiaux", "parent": "C"} +{"index": {"_id": "33.17Z"}} +{"name": "Réparation et maintenance d'autres équipements de transport", "parent": "C"} +{"index": {"_id": "33.19Z"}} +{"name": "Réparation d'autres équipements", "parent": "C"} +{"index": {"_id": "33.20A"}} +{"name": "Installation de structures métalliques, chaudronnées et de tuyauterie", "parent": "C"} +{"index": {"_id": "33.20B"}} +{"name": "Installation de machines et équipements mécaniques", "parent": "C"} +{"index": {"_id": "33.20C"}} +{"name": "Conception d'ensemble et assemblage sur site industriel d'équipements de contrôle des processus industriels", "parent": "C"} +{"index": {"_id": "33.20D"}} +{"name": "Installation d'équipements électriques, de matériels électroniques et optiques ou d'autres matériels", "parent": "C"} +{"index": {"_id": "D"}} +{"name": "Production et distribution d'électricité, de gaz, de vapeur et d'air conditionné", "parent": null} +{"index": {"_id": "35.11Z"}} +{"name": "Production d'électricité", "parent": "D"} +{"index": {"_id": "35.12Z"}} +{"name": "Transport d'électricité", "parent": "D"} +{"index": {"_id": "35.13Z"}} +{"name": "Distribution d'électricité", "parent": "D"} +{"index": {"_id": "35.14Z"}} +{"name": "Commerce d'électricité", "parent": "D"} +{"index": {"_id": "35.21Z"}} +{"name": "Production de combustibles gazeux", "parent": "D"} +{"index": {"_id": "35.22Z"}} +{"name": "Distribution de combustibles gazeux par conduites", "parent": "D"} +{"index": {"_id": "35.23Z"}} +{"name": "Commerce de combustibles gazeux par conduites", "parent": "D"} +{"index": {"_id": "35.30Z"}} +{"name": "Production et distribution de vapeur et d'air conditionné", "parent": "D"} +{"index": {"_id": "E"}} +{"name": "Production et distribution d'eau ; assainissement, gestion des déchets et dépollution", "parent": null} +{"index": {"_id": "36.00Z"}} +{"name": "Captage, traitement et distribution d'eau", "parent": "E"} +{"index": {"_id": "37.00Z"}} +{"name": "Collecte et traitement des eaux usées", "parent": "E"} +{"index": {"_id": "38.11Z"}} +{"name": "Collecte des déchets non dangereux", "parent": "E"} +{"index": {"_id": "38.12Z"}} +{"name": "Collecte des déchets dangereux", "parent": "E"} +{"index": {"_id": "38.21Z"}} +{"name": "Traitement et élimination des déchets non dangereux", "parent": "E"} +{"index": {"_id": "38.22Z"}} +{"name": "Traitement et élimination des déchets dangereux", "parent": "E"} +{"index": {"_id": "38.31Z"}} +{"name": "Démantèlement d'épaves", "parent": "E"} +{"index": {"_id": "38.32Z"}} +{"name": "Récupération de déchets triés", "parent": "E"} +{"index": {"_id": "39.00Z"}} +{"name": "Dépollution et autres services de gestion des déchets", "parent": "E"} +{"index": {"_id": "F"}} +{"name": "Construction", "parent": null} +{"index": {"_id": "41.10A"}} +{"name": "Promotion immobilière de logements", "parent": "F"} +{"index": {"_id": "41.10B"}} +{"name": "Promotion immobilière de bureaux", "parent": "F"} +{"index": {"_id": "41.10C"}} +{"name": "Promotion immobilière d'autres bâtiments", "parent": "F"} +{"index": {"_id": "41.10D"}} +{"name": "Supports juridiques de programmes", "parent": "F"} +{"index": {"_id": "41.20A"}} +{"name": "Construction de maisons individuelles", "parent": "F"} +{"index": {"_id": "41.20B"}} +{"name": "Construction d'autres bâtiments", "parent": "F"} +{"index": {"_id": "42.11Z"}} +{"name": "Construction de routes et autoroutes", "parent": "F"} +{"index": {"_id": "42.12Z"}} +{"name": "Construction de voies ferrées de surface et souterraines", "parent": "F"} +{"index": {"_id": "42.13A"}} +{"name": "Construction d'ouvrages d'art", "parent": "F"} +{"index": {"_id": "42.13B"}} +{"name": "Construction et entretien de tunnels", "parent": "F"} +{"index": {"_id": "42.21Z"}} +{"name": "Construction de réseaux pour fluides", "parent": "F"} +{"index": {"_id": "42.22Z"}} +{"name": "Construction de réseaux électriques et de télécommunications", "parent": "F"} +{"index": {"_id": "42.91Z"}} +{"name": "Construction d'ouvrages maritimes et fluviaux", "parent": "F"} +{"index": {"_id": "42.99Z"}} +{"name": "Construction d'autres ouvrages de génie civil n.c.a.", "parent": "F"} +{"index": {"_id": "43.11Z"}} +{"name": "Travaux de démolition", "parent": "F"} +{"index": {"_id": "43.12A"}} +{"name": "Travaux de terrassement courants et travaux préparatoires", "parent": "F"} +{"index": {"_id": "43.12B"}} +{"name": "Travaux de terrassement spécialisés ou de grande masse", "parent": "F"} +{"index": {"_id": "43.13Z"}} +{"name": "Forages et sondages", "parent": "F"} +{"index": {"_id": "43.21A"}} +{"name": "Travaux d'installation électrique dans tous locaux", "parent": "F"} +{"index": {"_id": "43.21B"}} +{"name": "Travaux d'installation électrique sur la voie publique", "parent": "F"} +{"index": {"_id": "43.22A"}} +{"name": "Travaux d'installation d'eau et de gaz en tous locaux", "parent": "F"} +{"index": {"_id": "43.22B"}} +{"name": "Travaux d'installation d'équipements thermiques et de climatisation", "parent": "F"} +{"index": {"_id": "43.29A"}} +{"name": "Travaux d'isolation", "parent": "F"} +{"index": {"_id": "43.29B"}} +{"name": "Autres travaux d'installation n.c.a.", "parent": "F"} +{"index": {"_id": "43.31Z"}} +{"name": "Travaux de plâtrerie", "parent": "F"} +{"index": {"_id": "43.32A"}} +{"name": "Travaux de menuiserie bois et PVC", "parent": "F"} +{"index": {"_id": "43.32B"}} +{"name": "Travaux de menuiserie métallique et serrurerie", "parent": "F"} +{"index": {"_id": "43.32C"}} +{"name": "Agencement de lieux de vente", "parent": "F"} +{"index": {"_id": "43.33Z"}} +{"name": "Travaux de revêtement des sols et des murs", "parent": "F"} +{"index": {"_id": "43.34Z"}} +{"name": "Travaux de peinture et vitrerie", "parent": "F"} +{"index": {"_id": "43.39Z"}} +{"name": "Autres travaux de finition", "parent": "F"} +{"index": {"_id": "43.91A"}} +{"name": "Travaux de charpente", "parent": "F"} +{"index": {"_id": "43.91B"}} +{"name": "Travaux de couverture par éléments", "parent": "F"} +{"index": {"_id": "43.99A"}} +{"name": "Travaux d'étanchéification", "parent": "F"} +{"index": {"_id": "43.99B"}} +{"name": "Travaux de montage de structures métalliques", "parent": "F"} +{"index": {"_id": "43.99C"}} +{"name": "Travaux de maçonnerie générale et gros œuvre de bâtiment", "parent": "F"} +{"index": {"_id": "43.99D"}} +{"name": "Autres travaux spécialisés de construction", "parent": "F"} +{"index": {"_id": "43.99E"}} +{"name": "Location avec opérateur de matériel de construction", "parent": "F"} +{"index": {"_id": "G"}} +{"name": "Commerce ; réparation d'automobiles et de motocycles", "parent": null} +{"index": {"_id": "45.11Z"}} +{"name": "Commerce de voitures et de véhicules automobiles légers", "parent": "G"} +{"index": {"_id": "45.19Z"}} +{"name": "Commerce d'autres véhicules automobiles", "parent": "G"} +{"index": {"_id": "45.20A"}} +{"name": "Entretien et réparation de véhicules automobiles légers", "parent": "G"} +{"index": {"_id": "45.20B"}} +{"name": "Entretien et réparation d'autres véhicules automobiles", "parent": "G"} +{"index": {"_id": "45.31Z"}} +{"name": "Commerce de gros d'équipements automobiles", "parent": "G"} +{"index": {"_id": "45.32Z"}} +{"name": "Commerce de détail d'équipements automobiles", "parent": "G"} +{"index": {"_id": "45.40Z"}} +{"name": "Commerce et réparation de motocycles", "parent": "G"} +{"index": {"_id": "46.11Z"}} +{"name": "Intermédiaires du commerce en matières premières agricoles, animaux vivants, matières premières textiles et produits semi-finis", "parent": "G"} +{"index": {"_id": "46.12A"}} +{"name": "Centrales d'achat de carburant", "parent": "G"} +{"index": {"_id": "46.12B"}} +{"name": "Autres intermédiaires du commerce en combustibles, métaux, minéraux et produits chimiques", "parent": "G"} +{"index": {"_id": "46.13Z"}} +{"name": "Intermédiaires du commerce en bois et matériaux de construction", "parent": "G"} +{"index": {"_id": "46.14Z"}} +{"name": "Intermédiaires du commerce en machines, équipements industriels, navires et avions", "parent": "G"} +{"index": {"_id": "46.15Z"}} +{"name": "Intermédiaires du commerce en meubles, articles de ménage et quincaillerie", "parent": "G"} +{"index": {"_id": "46.16Z"}} +{"name": "Intermédiaires du commerce en textiles, habillement, fourrures, chaussures et articles en cuir", "parent": "G"} +{"index": {"_id": "46.17A"}} +{"name": "Centrales d'achat alimentaires", "parent": "G"} +{"index": {"_id": "46.17B"}} +{"name": "Autres intermédiaires du commerce en denrées, boissons et tabac", "parent": "G"} +{"index": {"_id": "46.18Z"}} +{"name": "Intermédiaires spécialisés dans le commerce d'autres produits spécifiques", "parent": "G"} +{"index": {"_id": "46.19A"}} +{"name": "Centrales d'achat non alimentaires", "parent": "G"} +{"index": {"_id": "46.19B"}} +{"name": "Autres intermédiaires du commerce en produits divers", "parent": "G"} +{"index": {"_id": "46.21Z"}} +{"name": "Commerce de gros (commerce interentreprises) de céréales, de tabac non manufacturé, de semences et d'aliments pour le bétail", "parent": "G"} +{"index": {"_id": "46.22Z"}} +{"name": "Commerce de gros (commerce interentreprises) de fleurs et plantes", "parent": "G"} +{"index": {"_id": "46.23Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'animaux vivants", "parent": "G"} +{"index": {"_id": "46.24Z"}} +{"name": "Commerce de gros (commerce interentreprises) de cuirs et peaux", "parent": "G"} +{"index": {"_id": "46.31Z"}} +{"name": "Commerce de gros (commerce interentreprises) de fruits et légumes", "parent": "G"} +{"index": {"_id": "46.32A"}} +{"name": "Commerce de gros (commerce interentreprises) de viandes de boucherie", "parent": "G"} +{"index": {"_id": "46.32B"}} +{"name": "Commerce de gros (commerce interentreprises) de produits à base de viande", "parent": "G"} +{"index": {"_id": "46.32C"}} +{"name": "Commerce de gros (commerce interentreprises) de volailles et gibier", "parent": "G"} +{"index": {"_id": "46.33Z"}} +{"name": "Commerce de gros (commerce interentreprises) de produits laitiers, œufs, huiles et matières grasses comestibles", "parent": "G"} +{"index": {"_id": "46.34Z"}} +{"name": "Commerce de gros (commerce interentreprises) de boissons", "parent": "G"} +{"index": {"_id": "46.35Z"}} +{"name": "Commerce de gros (commerce interentreprises) de produits à base de tabac", "parent": "G"} +{"index": {"_id": "46.36Z"}} +{"name": "Commerce de gros (commerce interentreprises) de sucre, chocolat et confiserie", "parent": "G"} +{"index": {"_id": "46.37Z"}} +{"name": "Commerce de gros (commerce interentreprises) de café, thé, cacao et épices", "parent": "G"} +{"index": {"_id": "46.38A"}} +{"name": "Commerce de gros (commerce interentreprises) de poissons, crustacés et mollusques", "parent": "G"} +{"index": {"_id": "46.38B"}} +{"name": "Commerce de gros (commerce interentreprises) alimentaire spécialisé divers", "parent": "G"} +{"index": {"_id": "46.39A"}} +{"name": "Commerce de gros (commerce interentreprises) de produits surgelés", "parent": "G"} +{"index": {"_id": "46.39B"}} +{"name": "Commerce de gros (commerce interentreprises) alimentaire non spécialisé", "parent": "G"} +{"index": {"_id": "46.41Z"}} +{"name": "Commerce de gros (commerce interentreprises) de textiles", "parent": "G"} +{"index": {"_id": "46.42Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'habillement et de chaussures", "parent": "G"} +{"index": {"_id": "46.43Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'appareils électroménagers", "parent": "G"} +{"index": {"_id": "46.44Z"}} +{"name": "Commerce de gros (commerce interentreprises) de vaisselle, verrerie et produits d'entretien", "parent": "G"} +{"index": {"_id": "46.45Z"}} +{"name": "Commerce de gros (commerce interentreprises) de parfumerie et de produits de beauté", "parent": "G"} +{"index": {"_id": "46.46Z"}} +{"name": "Commerce de gros (commerce interentreprises) de produits pharmaceutiques", "parent": "G"} +{"index": {"_id": "46.47Z"}} +{"name": "Commerce de gros (commerce interentreprises) de meubles, de tapis et d'appareils d'éclairage", "parent": "G"} +{"index": {"_id": "46.48Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'articles d'horlogerie et de bijouterie", "parent": "G"} +{"index": {"_id": "46.49Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'autres biens domestiques", "parent": "G"} +{"index": {"_id": "46.51Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'ordinateurs, d'équipements informatiques périphériques et de logiciels", "parent": "G"} +{"index": {"_id": "46.52Z"}} +{"name": "Commerce de gros (commerce interentreprises) de composants et d'équipements électroniques et de télécommunication", "parent": "G"} +{"index": {"_id": "46.61Z"}} +{"name": "Commerce de gros (commerce interentreprises) de matériel agricole", "parent": "G"} +{"index": {"_id": "46.62Z"}} +{"name": "Commerce de gros (commerce interentreprises) de machines-outils", "parent": "G"} +{"index": {"_id": "46.63Z"}} +{"name": "Commerce de gros (commerce interentreprises) de machines pour l'extraction, la construction et le génie civil", "parent": "G"} +{"index": {"_id": "46.64Z"}} +{"name": "Commerce de gros (commerce interentreprises) de machines pour l'industrie textile et l'habillement", "parent": "G"} +{"index": {"_id": "46.65Z"}} +{"name": "Commerce de gros (commerce interentreprises) de mobilier de bureau", "parent": "G"} +{"index": {"_id": "46.66Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'autres machines et équipements de bureau", "parent": "G"} +{"index": {"_id": "46.69A"}} +{"name": "Commerce de gros (commerce interentreprises) de matériel électrique", "parent": "G"} +{"index": {"_id": "46.69B"}} +{"name": "Commerce de gros (commerce interentreprises) de fournitures et équipements industriels divers", "parent": "G"} +{"index": {"_id": "46.69C"}} +{"name": "Commerce de gros (commerce interentreprises) de fournitures et équipements divers pour le commerce et les services", "parent": "G"} +{"index": {"_id": "46.71Z"}} +{"name": "Commerce de gros (commerce interentreprises) de combustibles et de produits annexes", "parent": "G"} +{"index": {"_id": "46.72Z"}} +{"name": "Commerce de gros (commerce interentreprises) de minerais et métaux", "parent": "G"} +{"index": {"_id": "46.73A"}} +{"name": "Commerce de gros (commerce interentreprises) de bois et de matériaux de construction", "parent": "G"} +{"index": {"_id": "46.73B"}} +{"name": "Commerce de gros (commerce interentreprises) d'appareils sanitaires et de produits de décoration", "parent": "G"} +{"index": {"_id": "46.74A"}} +{"name": "Commerce de gros (commerce interentreprises) de quincaillerie", "parent": "G"} +{"index": {"_id": "46.74B"}} +{"name": "Commerce de gros (commerce interentreprises) de fournitures pour la plomberie et le chauffage", "parent": "G"} +{"index": {"_id": "46.75Z"}} +{"name": "Commerce de gros (commerce interentreprises) de produits chimiques", "parent": "G"} +{"index": {"_id": "46.76Z"}} +{"name": "Commerce de gros (commerce interentreprises) d'autres produits intermédiaires", "parent": "G"} +{"index": {"_id": "46.77Z"}} +{"name": "Commerce de gros (commerce interentreprises) de déchets et débris", "parent": "G"} +{"index": {"_id": "46.90Z"}} +{"name": "Commerce de gros (commerce interentreprises) non spécialisé", "parent": "G"} +{"index": {"_id": "47.11A"}} +{"name": "Commerce de détail de produits surgelés", "parent": "G"} +{"index": {"_id": "47.11B"}} +{"name": "Commerce d'alimentation générale", "parent": "G"} +{"index": {"_id": "47.11C"}} +{"name": "Supérettes", "parent": "G"} +{"index": {"_id": "47.11D"}} +{"name": "Supermarchés", "parent": "G"} +{"index": {"_id": "47.11E"}} +{"name": "Magasins multi-commerces", "parent": "G"} +{"index": {"_id": "47.11F"}} +{"name": "Hypermarchés", "parent": "G"} +{"index": {"_id": "47.19A"}} +{"name": "Grands magasins", "parent": "G"} +{"index": {"_id": "47.19B"}} +{"name": "Autres commerces de détail en magasin non spécialisé", "parent": "G"} +{"index": {"_id": "47.21Z"}} +{"name": "Commerce de détail de fruits et légumes en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.22Z"}} +{"name": "Commerce de détail de viandes et de produits à base de viande en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.23Z"}} +{"name": "Commerce de détail de poissons, crustacés et mollusques en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.24Z"}} +{"name": "Commerce de détail de pain, pâtisserie et confiserie en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.25Z"}} +{"name": "Commerce de détail de boissons en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.26Z"}} +{"name": "Commerce de détail de produits à base de tabac en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.29Z"}} +{"name": "Autres commerces de détail alimentaires en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.30Z"}} +{"name": "Commerce de détail de carburants en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.41Z"}} +{"name": "Commerce de détail d'ordinateurs, d'unités périphériques et de logiciels en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.42Z"}} +{"name": "Commerce de détail de matériels de télécommunication en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.43Z"}} +{"name": "Commerce de détail de matériels audio et vidéo en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.51Z"}} +{"name": "Commerce de détail de textiles en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.52A"}} +{"name": "Commerce de détail de quincaillerie, peintures et verres en petites surfaces (moins de 400 m²)", "parent": "G"} +{"index": {"_id": "47.52B"}} +{"name": "Commerce de détail de quincaillerie, peintures et verres en grandes surfaces (400 m² et plus)", "parent": "G"} +{"index": {"_id": "47.53Z"}} +{"name": "Commerce de détail de tapis, moquettes et revêtements de murs et de sols en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.54Z"}} +{"name": "Commerce de détail d'appareils électroménagers en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.59A"}} +{"name": "Commerce de détail de meubles", "parent": "G"} +{"index": {"_id": "47.59B"}} +{"name": "Commerce de détail d'autres équipements du foyer", "parent": "G"} +{"index": {"_id": "47.61Z"}} +{"name": "Commerce de détail de livres en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.62Z"}} +{"name": "Commerce de détail de journaux et papeterie en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.63Z"}} +{"name": "Commerce de détail d'enregistrements musicaux et vidéo en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.64Z"}} +{"name": "Commerce de détail d'articles de sport en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.65Z"}} +{"name": "Commerce de détail de jeux et jouets en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.71Z"}} +{"name": "Commerce de détail d'habillement en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.72A"}} +{"name": "Commerce de détail de la chaussure", "parent": "G"} +{"index": {"_id": "47.72B"}} +{"name": "Commerce de détail de maroquinerie et d'articles de voyage", "parent": "G"} +{"index": {"_id": "47.73Z"}} +{"name": "Commerce de détail de produits pharmaceutiques en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.74Z"}} +{"name": "Commerce de détail d'articles médicaux et orthopédiques en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.75Z"}} +{"name": "Commerce de détail de parfumerie et de produits de beauté en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.76Z"}} +{"name": "Commerce de détail de fleurs, plantes, graines, engrais, animaux de compagnie et aliments pour ces animaux en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.77Z"}} +{"name": "Commerce de détail d'articles d'horlogerie et de bijouterie en magasin spécialisé", "parent": "G"} +{"index": {"_id": "47.78A"}} +{"name": "Commerces de détail d'optique", "parent": "G"} +{"index": {"_id": "47.78B"}} +{"name": "Commerces de détail de charbons et combustibles", "parent": "G"} +{"index": {"_id": "47.78C"}} +{"name": "Autres commerces de détail spécialisés divers", "parent": "G"} +{"index": {"_id": "47.79Z"}} +{"name": "Commerce de détail de biens d'occasion en magasin", "parent": "G"} +{"index": {"_id": "47.81Z"}} +{"name": "Commerce de détail alimentaire sur éventaires et marchés", "parent": "G"} +{"index": {"_id": "47.82Z"}} +{"name": "Commerce de détail de textiles, d'habillement et de chaussures sur éventaires et marchés", "parent": "G"} +{"index": {"_id": "47.89Z"}} +{"name": "Autres commerces de détail sur éventaires et marchés", "parent": "G"} +{"index": {"_id": "47.91A"}} +{"name": "Vente à distance sur catalogue général", "parent": "G"} +{"index": {"_id": "47.91B"}} +{"name": "Vente à distance sur catalogue spécialisé", "parent": "G"} +{"index": {"_id": "47.99A"}} +{"name": "Vente à domicile", "parent": "G"} +{"index": {"_id": "47.99B"}} +{"name": "Vente par automates et autres commerces de détail hors magasin, éventaires ou marchés n.c.a.", "parent": "G"} +{"index": {"_id": "H"}} +{"name": "Transports et entreposage", "parent": null} +{"index": {"_id": "49.10Z"}} +{"name": "Transport ferroviaire interurbain de voyageurs", "parent": "H"} +{"index": {"_id": "49.20Z"}} +{"name": "Transports ferroviaires de fret", "parent": "H"} +{"index": {"_id": "49.31Z"}} +{"name": "Transports urbains et suburbains de voyageurs", "parent": "H"} +{"index": {"_id": "49.32Z"}} +{"name": "Transports de voyageurs par taxis", "parent": "H"} +{"index": {"_id": "49.39A"}} +{"name": "Transports routiers réguliers de voyageurs", "parent": "H"} +{"index": {"_id": "49.39B"}} +{"name": "Autres transports routiers de voyageurs", "parent": "H"} +{"index": {"_id": "49.39C"}} +{"name": "Téléphériques et remontées mécaniques", "parent": "H"} +{"index": {"_id": "49.41A"}} +{"name": "Transports routiers de fret interurbains", "parent": "H"} +{"index": {"_id": "49.41B"}} +{"name": "Transports routiers de fret de proximité", "parent": "H"} +{"index": {"_id": "49.41C"}} +{"name": "Location de camions avec chauffeur", "parent": "H"} +{"index": {"_id": "49.42Z"}} +{"name": "Services de déménagement", "parent": "H"} +{"index": {"_id": "49.50Z"}} +{"name": "Transports par conduites", "parent": "H"} +{"index": {"_id": "50.10Z"}} +{"name": "Transports maritimes et côtiers de passagers", "parent": "H"} +{"index": {"_id": "50.20Z"}} +{"name": "Transports maritimes et côtiers de fret", "parent": "H"} +{"index": {"_id": "50.30Z"}} +{"name": "Transports fluviaux de passagers", "parent": "H"} +{"index": {"_id": "50.40Z"}} +{"name": "Transports fluviaux de fret", "parent": "H"} +{"index": {"_id": "51.10Z"}} +{"name": "Transports aériens de passagers", "parent": "H"} +{"index": {"_id": "51.21Z"}} +{"name": "Transports aériens de fret", "parent": "H"} +{"index": {"_id": "51.22Z"}} +{"name": "Transports spatiaux", "parent": "H"} +{"index": {"_id": "52.10A"}} +{"name": "Entreposage et stockage frigorifique", "parent": "H"} +{"index": {"_id": "52.10B"}} +{"name": "Entreposage et stockage non frigorifique", "parent": "H"} +{"index": {"_id": "52.21Z"}} +{"name": "Services auxiliaires des transports terrestres", "parent": "H"} +{"index": {"_id": "52.22Z"}} +{"name": "Services auxiliaires des transports par eau", "parent": "H"} +{"index": {"_id": "52.23Z"}} +{"name": "Services auxiliaires des transports aériens", "parent": "H"} +{"index": {"_id": "52.24A"}} +{"name": "Manutention portuaire", "parent": "H"} +{"index": {"_id": "52.24B"}} +{"name": "Manutention non portuaire", "parent": "H"} +{"index": {"_id": "52.29A"}} +{"name": "Messagerie, fret express", "parent": "H"} +{"index": {"_id": "52.29B"}} +{"name": "Affrètement et organisation des transports", "parent": "H"} +{"index": {"_id": "53.10Z"}} +{"name": "Activités de poste dans le cadre d'une obligation de service universel", "parent": "H"} +{"index": {"_id": "53.20Z"}} +{"name": "Autres activités de poste et de courrier", "parent": "H"} +{"index": {"_id": "I"}} +{"name": "Hébergement et restauration", "parent": null} +{"index": {"_id": "55.10Z"}} +{"name": "Hôtels et hébergement similaire", "parent": "I"} +{"index": {"_id": "55.20Z"}} +{"name": "Hébergement touristique et autre hébergement de courte durée", "parent": "I"} +{"index": {"_id": "55.30Z"}} +{"name": "Terrains de camping et parcs pour caravanes ou véhicules de loisirs", "parent": "I"} +{"index": {"_id": "55.90Z"}} +{"name": "Autres hébergements", "parent": "I"} +{"index": {"_id": "56.10A"}} +{"name": "Restauration traditionnelle", "parent": "I"} +{"index": {"_id": "56.10B"}} +{"name": "Cafétérias et autres libres-services", "parent": "I"} +{"index": {"_id": "56.10C"}} +{"name": "Restauration de type rapide", "parent": "I"} +{"index": {"_id": "56.21Z"}} +{"name": "Services des traiteurs", "parent": "I"} +{"index": {"_id": "56.29A"}} +{"name": "Restauration collective sous contrat", "parent": "I"} +{"index": {"_id": "56.29B"}} +{"name": "Autres services de restauration n.c.a.", "parent": "I"} +{"index": {"_id": "56.30Z"}} +{"name": "Débits de boissons", "parent": "I"} +{"index": {"_id": "J"}} +{"name": "Information et communication", "parent": null} +{"index": {"_id": "58.11Z"}} +{"name": "Édition de livres", "parent": "J"} +{"index": {"_id": "58.12Z"}} +{"name": "Édition de répertoires et de fichiers d'adresses", "parent": "J"} +{"index": {"_id": "58.13Z"}} +{"name": "Édition de journaux", "parent": "J"} +{"index": {"_id": "58.14Z"}} +{"name": "Édition de revues et périodiques", "parent": "J"} +{"index": {"_id": "58.19Z"}} +{"name": "Autres activités d'édition", "parent": "J"} +{"index": {"_id": "58.21Z"}} +{"name": "Édition de jeux électroniques", "parent": "J"} +{"index": {"_id": "58.29A"}} +{"name": "Édition de logiciels système et de réseau", "parent": "J"} +{"index": {"_id": "58.29B"}} +{"name": "Édition de logiciels outils de développement et de langages", "parent": "J"} +{"index": {"_id": "58.29C"}} +{"name": "Édition de logiciels applicatifs", "parent": "J"} +{"index": {"_id": "59.11A"}} +{"name": "Production de films et de programmes pour la télévision", "parent": "J"} +{"index": {"_id": "59.11B"}} +{"name": "Production de films institutionnels et publicitaires", "parent": "J"} +{"index": {"_id": "59.11C"}} +{"name": "Production de films pour le cinéma", "parent": "J"} +{"index": {"_id": "59.12Z"}} +{"name": "Post-production de films cinématographiques, de vidéo et de programmes de télévision", "parent": "J"} +{"index": {"_id": "59.13A"}} +{"name": "Distribution de films cinématographiques", "parent": "J"} +{"index": {"_id": "59.13B"}} +{"name": "Édition et distribution vidéo", "parent": "J"} +{"index": {"_id": "59.14Z"}} +{"name": "Projection de films cinématographiques", "parent": "J"} +{"index": {"_id": "59.20Z"}} +{"name": "Enregistrement sonore et édition musicale", "parent": "J"} +{"index": {"_id": "60.10Z"}} +{"name": "Édition et diffusion de programmes radio", "parent": "J"} +{"index": {"_id": "60.20A"}} +{"name": "Édition de chaînes généralistes", "parent": "J"} +{"index": {"_id": "60.20B"}} +{"name": "Édition de chaînes thématiques", "parent": "J"} +{"index": {"_id": "61.10Z"}} +{"name": "Télécommunications filaires", "parent": "J"} +{"index": {"_id": "61.20Z"}} +{"name": "Télécommunications sans fil", "parent": "J"} +{"index": {"_id": "61.30Z"}} +{"name": "Télécommunications par satellite", "parent": "J"} +{"index": {"_id": "61.90Z"}} +{"name": "Autres activités de télécommunication", "parent": "J"} +{"index": {"_id": "62.01Z"}} +{"name": "Programmation informatique", "parent": "J"} +{"index": {"_id": "62.02A"}} +{"name": "Conseil en systèmes et logiciels informatiques", "parent": "J"} +{"index": {"_id": "62.02B"}} +{"name": "Tierce maintenance de systèmes et d'applications informatiques", "parent": "J"} +{"index": {"_id": "62.03Z"}} +{"name": "Gestion d'installations informatiques", "parent": "J"} +{"index": {"_id": "62.09Z"}} +{"name": "Autres activités informatiques", "parent": "J"} +{"index": {"_id": "63.11Z"}} +{"name": "Traitement de données, hébergement et activités connexes", "parent": "J"} +{"index": {"_id": "63.12Z"}} +{"name": "Portails Internet", "parent": "J"} +{"index": {"_id": "63.91Z"}} +{"name": "Activités des agences de presse", "parent": "J"} +{"index": {"_id": "63.99Z"}} +{"name": "Autres services d'information n.c.a.", "parent": "J"} +{"index": {"_id": "K"}} +{"name": "Activités financières et d'assurance", "parent": null} +{"index": {"_id": "64.11Z"}} +{"name": "Activités de banque centrale", "parent": "K"} +{"index": {"_id": "64.19Z"}} +{"name": "Autres intermédiations monétaires", "parent": "K"} +{"index": {"_id": "64.20Z"}} +{"name": "Activités des sociétés holding", "parent": "K"} +{"index": {"_id": "64.30Z"}} +{"name": "Fonds de placement et entités financières similaires", "parent": "K"} +{"index": {"_id": "64.91Z"}} +{"name": "Crédit-bail", "parent": "K"} +{"index": {"_id": "64.92Z"}} +{"name": "Autre distribution de crédit", "parent": "K"} +{"index": {"_id": "64.99Z"}} +{"name": "Autres activités des services financiers, hors assurance et caisses de retraite, n.c.a.", "parent": "K"} +{"index": {"_id": "65.11Z"}} +{"name": "Assurance vie", "parent": "K"} +{"index": {"_id": "65.12Z"}} +{"name": "Autres assurances", "parent": "K"} +{"index": {"_id": "65.20Z"}} +{"name": "Réassurance", "parent": "K"} +{"index": {"_id": "65.30Z"}} +{"name": "Caisses de retraite", "parent": "K"} +{"index": {"_id": "66.11Z"}} +{"name": "Administration de marchés financiers", "parent": "K"} +{"index": {"_id": "66.12Z"}} +{"name": "Courtage de valeurs mobilières et de marchandises", "parent": "K"} +{"index": {"_id": "66.19A"}} +{"name": "Supports juridiques de gestion de patrimoine mobilier", "parent": "K"} +{"index": {"_id": "66.19B"}} +{"name": "Autres activités auxiliaires de services financiers, hors assurance et caisses de retraite, n.c.a.", "parent": "K"} +{"index": {"_id": "66.21Z"}} +{"name": "Évaluation des risques et dommages", "parent": "K"} +{"index": {"_id": "66.22Z"}} +{"name": "Activités des agents et courtiers d'assurances", "parent": "K"} +{"index": {"_id": "66.29Z"}} +{"name": "Autres activités auxiliaires d'assurance et de caisses de retraite", "parent": "K"} +{"index": {"_id": "66.30Z"}} +{"name": "Gestion de fonds", "parent": "K"} +{"index": {"_id": "L"}} +{"name": "Activités immobilières", "parent": null} +{"index": {"_id": "68.10Z"}} +{"name": "Activités des marchands de biens immobiliers", "parent": "L"} +{"index": {"_id": "68.20A"}} +{"name": "Location de logements", "parent": "L"} +{"index": {"_id": "68.20B"}} +{"name": "Location de terrains et d'autres biens immobiliers", "parent": "L"} +{"index": {"_id": "68.31Z"}} +{"name": "Agences immobilières", "parent": "L"} +{"index": {"_id": "68.32A"}} +{"name": "Administration d'immeubles et autres biens immobiliers", "parent": "L"} +{"index": {"_id": "68.32B"}} +{"name": "Supports juridiques de gestion de patrimoine immobilier", "parent": "L"} +{"index": {"_id": "M"}} +{"name": "Activités spécialisées, scientifiques et techniques", "parent": null} +{"index": {"_id": "69.10Z"}} +{"name": "Activités juridiques", "parent": "M"} +{"index": {"_id": "69.20Z"}} +{"name": "Activités comptables", "parent": "M"} +{"index": {"_id": "70.10Z"}} +{"name": "Activités des sièges sociaux", "parent": "M"} +{"index": {"_id": "70.21Z"}} +{"name": "Conseil en relations publiques et communication", "parent": "M"} +{"index": {"_id": "70.22Z"}} +{"name": "Conseil pour les affaires et autres conseils de gestion", "parent": "M"} +{"index": {"_id": "71.11Z"}} +{"name": "Activités d'architecture", "parent": "M"} +{"index": {"_id": "71.12A"}} +{"name": "Activité des géomètres", "parent": "M"} +{"index": {"_id": "71.12B"}} +{"name": "Ingénierie, études techniques", "parent": "M"} +{"index": {"_id": "71.20A"}} +{"name": "Contrôle technique automobile", "parent": "M"} +{"index": {"_id": "71.20B"}} +{"name": "Analyses, essais et inspections techniques", "parent": "M"} +{"index": {"_id": "72.11Z"}} +{"name": "Recherche-développement en biotechnologie", "parent": "M"} +{"index": {"_id": "72.19Z"}} +{"name": "Recherche-développement en autres sciences physiques et naturelles", "parent": "M"} +{"index": {"_id": "72.20Z"}} +{"name": "Recherche-développement en sciences humaines et sociales", "parent": "M"} +{"index": {"_id": "73.11Z"}} +{"name": "Activités des agences de publicité", "parent": "M"} +{"index": {"_id": "73.12Z"}} +{"name": "Régie publicitaire de médias", "parent": "M"} +{"index": {"_id": "73.20Z"}} +{"name": "Études de marché et sondages", "parent": "M"} +{"index": {"_id": "74.10Z"}} +{"name": "Activités spécialisées de design", "parent": "M"} +{"index": {"_id": "74.20Z"}} +{"name": "Activités photographiques", "parent": "M"} +{"index": {"_id": "74.30Z"}} +{"name": "Traduction et interprétation", "parent": "M"} +{"index": {"_id": "74.90A"}} +{"name": "Activité des économistes de la construction", "parent": "M"} +{"index": {"_id": "74.90B"}} +{"name": "Activités spécialisées, scientifiques et techniques diverses", "parent": "M"} +{"index": {"_id": "N"}} +{"name": "Activités de services administratifs et de soutien", "parent": null} +{"index": {"_id": "75.00Z"}} +{"name": "Activités vétérinaires", "parent": "N"} +{"index": {"_id": "77.11A"}} +{"name": "Location de courte durée de voitures et de véhicules automobiles légers", "parent": "N"} +{"index": {"_id": "77.11B"}} +{"name": "Location de longue durée de voitures et de véhicules automobiles légers", "parent": "N"} +{"index": {"_id": "77.12Z"}} +{"name": "Location et location-bail de camions", "parent": "N"} +{"index": {"_id": "77.21Z"}} +{"name": "Location et location-bail d'articles de loisirs et de sport", "parent": "N"} +{"index": {"_id": "77.22Z"}} +{"name": "Location de vidéocassettes et disques vidéo", "parent": "N"} +{"index": {"_id": "77.29Z"}} +{"name": "Location et location-bail d'autres biens personnels et domestiques", "parent": "N"} +{"index": {"_id": "77.31Z"}} +{"name": "Location et location-bail de machines et équipements agricoles", "parent": "N"} +{"index": {"_id": "77.32Z"}} +{"name": "Location et location-bail de machines et équipements pour la construction", "parent": "N"} +{"index": {"_id": "77.33Z"}} +{"name": "Location et location-bail de machines de bureau et de matériel informatique", "parent": "N"} +{"index": {"_id": "77.34Z"}} +{"name": "Location et location-bail de matériels de transport par eau", "parent": "N"} +{"index": {"_id": "77.35Z"}} +{"name": "Location et location-bail de matériels de transport aérien", "parent": "N"} +{"index": {"_id": "77.39Z"}} +{"name": "Location et location-bail d'autres machines, équipements et biens matériels n.c.a.", "parent": "N"} +{"index": {"_id": "77.40Z"}} +{"name": "Location-bail de propriété intellectuelle et de produits similaires, à l'exception des œuvres soumises à copyright", "parent": "N"} +{"index": {"_id": "78.10Z"}} +{"name": "Activités des agences de placement de main-d'œuvre", "parent": "N"} +{"index": {"_id": "78.20Z"}} +{"name": "Activités des agences de travail temporaire", "parent": "N"} +{"index": {"_id": "78.30Z"}} +{"name": "Autre mise à disposition de ressources humaines", "parent": "N"} +{"index": {"_id": "79.11Z"}} +{"name": "Activités des agences de voyage", "parent": "N"} +{"index": {"_id": "79.12Z"}} +{"name": "Activités des voyagistes", "parent": "N"} +{"index": {"_id": "79.90Z"}} +{"name": "Autres services de réservation et activités connexes", "parent": "N"} +{"index": {"_id": "80.10Z"}} +{"name": "Activités de sécurité privée", "parent": "N"} +{"index": {"_id": "80.20Z"}} +{"name": "Activités liées aux systèmes de sécurité", "parent": "N"} +{"index": {"_id": "80.30Z"}} +{"name": "Activités d'enquête", "parent": "N"} +{"index": {"_id": "81.10Z"}} +{"name": "Activités combinées de soutien lié aux bâtiments", "parent": "N"} +{"index": {"_id": "81.21Z"}} +{"name": "Nettoyage courant des bâtiments", "parent": "N"} +{"index": {"_id": "81.22Z"}} +{"name": "Autres activités de nettoyage des bâtiments et nettoyage industriel", "parent": "N"} +{"index": {"_id": "81.29A"}} +{"name": "Désinfection, désinsectisation, dératisation", "parent": "N"} +{"index": {"_id": "81.29B"}} +{"name": "Autres activités de nettoyage n.c.a.", "parent": "N"} +{"index": {"_id": "81.30Z"}} +{"name": "Services d'aménagement paysager", "parent": "N"} +{"index": {"_id": "82.11Z"}} +{"name": "Services administratifs combinés de bureau", "parent": "N"} +{"index": {"_id": "82.19Z"}} +{"name": "Photocopie, préparation de documents et autres activités spécialisées de soutien de bureau", "parent": "N"} +{"index": {"_id": "82.20Z"}} +{"name": "Activités de centres d'appels", "parent": "N"} +{"index": {"_id": "82.30Z"}} +{"name": "Organisation de foires, salons professionnels et congrès", "parent": "N"} +{"index": {"_id": "82.91Z"}} +{"name": "Activités des agences de recouvrement de factures et des sociétés d'information financière sur la clientèle", "parent": "N"} +{"index": {"_id": "82.92Z"}} +{"name": "Activités de conditionnement", "parent": "N"} +{"index": {"_id": "82.99Z"}} +{"name": "Autres activités de soutien aux entreprises n.c.a.", "parent": "N"} +{"index": {"_id": "O"}} +{"name": "Administration publique", "parent": null} +{"index": {"_id": "84.11Z"}} +{"name": "Administration publique générale", "parent": "O"} +{"index": {"_id": "84.12Z"}} +{"name": "Administration publique (tutelle) de la santé, de la formation, de la culture et des services sociaux, autre que sécurité sociale", "parent": "O"} +{"index": {"_id": "84.13Z"}} +{"name": "Administration publique (tutelle) des activités économiques", "parent": "O"} +{"index": {"_id": "84.21Z"}} +{"name": "Affaires étrangères", "parent": "O"} +{"index": {"_id": "84.22Z"}} +{"name": "Défense", "parent": "O"} +{"index": {"_id": "84.23Z"}} +{"name": "Justice", "parent": "O"} +{"index": {"_id": "84.24Z"}} +{"name": "Activités d'ordre public et de sécurité", "parent": "O"} +{"index": {"_id": "84.25Z"}} +{"name": "Services du feu et de secours", "parent": "O"} +{"index": {"_id": "84.30A"}} +{"name": "Activités générales de sécurité sociale", "parent": "O"} +{"index": {"_id": "84.30B"}} +{"name": "Gestion des retraites complémentaires", "parent": "O"} +{"index": {"_id": "84.30C"}} +{"name": "Distribution sociale de revenus", "parent": "O"} +{"index": {"_id": "P"}} +{"name": "Enseignement", "parent": null} +{"index": {"_id": "85.10Z"}} +{"name": "Enseignement pré-primaire", "parent": "P"} +{"index": {"_id": "85.20Z"}} +{"name": "Enseignement primaire", "parent": "P"} +{"index": {"_id": "85.31Z"}} +{"name": "Enseignement secondaire général", "parent": "P"} +{"index": {"_id": "85.32Z"}} +{"name": "Enseignement secondaire technique ou professionnel", "parent": "P"} +{"index": {"_id": "85.41Z"}} +{"name": "Enseignement post-secondaire non supérieur", "parent": "P"} +{"index": {"_id": "85.42Z"}} +{"name": "Enseignement supérieur", "parent": "P"} +{"index": {"_id": "85.51Z"}} +{"name": "Enseignement de disciplines sportives et d'activités de loisirs", "parent": "P"} +{"index": {"_id": "85.52Z"}} +{"name": "Enseignement culturel", "parent": "P"} +{"index": {"_id": "85.53Z"}} +{"name": "Enseignement de la conduite", "parent": "P"} +{"index": {"_id": "85.59A"}} +{"name": "Formation continue d'adultes", "parent": "P"} +{"index": {"_id": "85.59B"}} +{"name": "Autres enseignements", "parent": "P"} +{"index": {"_id": "85.60Z"}} +{"name": "Activités de soutien à l'enseignement", "parent": "P"} +{"index": {"_id": "Q"}} +{"name": "Santé humaine et action sociale", "parent": null} +{"index": {"_id": "86.10Z"}} +{"name": "Activités hospitalières", "parent": "Q"} +{"index": {"_id": "86.21Z"}} +{"name": "Activité des médecins généralistes", "parent": "Q"} +{"index": {"_id": "86.22A"}} +{"name": "Activités de radiodiagnostic et de radiothérapie", "parent": "Q"} +{"index": {"_id": "86.22B"}} +{"name": "Activités chirurgicales", "parent": "Q"} +{"index": {"_id": "86.22C"}} +{"name": "Autres activités des médecins spécialistes", "parent": "Q"} +{"index": {"_id": "86.23Z"}} +{"name": "Pratique dentaire", "parent": "Q"} +{"index": {"_id": "86.90A"}} +{"name": "Ambulances", "parent": "Q"} +{"index": {"_id": "86.90B"}} +{"name": "Laboratoires d'analyses médicales", "parent": "Q"} +{"index": {"_id": "86.90C"}} +{"name": "Centres de collecte et banques d'organes", "parent": "Q"} +{"index": {"_id": "86.90D"}} +{"name": "Activités des infirmiers et des sages-femmes", "parent": "Q"} +{"index": {"_id": "86.90E"}} +{"name": "Activités des professionnels de la rééducation, de l'appareillage et des pédicures-podologues", "parent": "Q"} +{"index": {"_id": "86.90F"}} +{"name": "Activités de santé humaine non classées ailleurs", "parent": "Q"} +{"index": {"_id": "87.10A"}} +{"name": "Hébergement médicalisé pour personnes âgées", "parent": "Q"} +{"index": {"_id": "87.10B"}} +{"name": "Hébergement médicalisé pour enfants handicapés", "parent": "Q"} +{"index": {"_id": "87.10C"}} +{"name": "Hébergement médicalisé pour adultes handicapés et autre hébergement médicalisé", "parent": "Q"} +{"index": {"_id": "87.20A"}} +{"name": "Hébergement social pour handicapés mentaux et malades mentaux", "parent": "Q"} +{"index": {"_id": "87.20B"}} +{"name": "Hébergement social pour toxicomanes", "parent": "Q"} +{"index": {"_id": "87.30A"}} +{"name": "Hébergement social pour personnes âgées", "parent": "Q"} +{"index": {"_id": "87.30B"}} +{"name": "Hébergement social pour handicapés physiques", "parent": "Q"} +{"index": {"_id": "87.90A"}} +{"name": "Hébergement social pour enfants en difficultés", "parent": "Q"} +{"index": {"_id": "87.90B"}} +{"name": "Hébergement social pour adultes et familles en difficultés et autre hébergement social", "parent": "Q"} +{"index": {"_id": "88.10A"}} +{"name": "Aide à domicile", "parent": "Q"} +{"index": {"_id": "88.10B"}} +{"name": "Accueil ou accompagnement sans hébergement d'adultes handicapés ou de personnes âgées", "parent": "Q"} +{"index": {"_id": "88.10C"}} +{"name": "Aide par le travail", "parent": "Q"} +{"index": {"_id": "88.91A"}} +{"name": "Accueil de jeunes enfants", "parent": "Q"} +{"index": {"_id": "88.91B"}} +{"name": "Accueil ou accompagnement sans hébergement d'enfants handicapés", "parent": "Q"} +{"index": {"_id": "88.99A"}} +{"name": "Autre accueil ou accompagnement sans hébergement d'enfants et d'adolescents", "parent": "Q"} +{"index": {"_id": "88.99B"}} +{"name": "Action sociale sans hébergement n.c.a.", "parent": "Q"} +{"index": {"_id": "R"}} +{"name": "Arts, spectacles et activités récréatives", "parent": null} +{"index": {"_id": "90.01Z"}} +{"name": "Arts du spectacle vivant", "parent": "R"} +{"index": {"_id": "90.02Z"}} +{"name": "Activités de soutien au spectacle vivant", "parent": "R"} +{"index": {"_id": "90.03A"}} +{"name": "Création artistique relevant des arts plastiques", "parent": "R"} +{"index": {"_id": "90.03B"}} +{"name": "Autre création artistique", "parent": "R"} +{"index": {"_id": "90.04Z"}} +{"name": "Gestion de salles de spectacles", "parent": "R"} +{"index": {"_id": "91.01Z"}} +{"name": "Gestion des bibliothèques et des archives", "parent": "R"} +{"index": {"_id": "91.02Z"}} +{"name": "Gestion des musées", "parent": "R"} +{"index": {"_id": "91.03Z"}} +{"name": "Gestion des sites et monuments historiques et des attractions touristiques similaires", "parent": "R"} +{"index": {"_id": "91.04Z"}} +{"name": "Gestion des jardins botaniques et zoologiques et des réserves naturelles", "parent": "R"} +{"index": {"_id": "92.00Z"}} +{"name": "Organisation de jeux de hasard et d'argent", "parent": "R"} +{"index": {"_id": "93.11Z"}} +{"name": "Gestion d'installations sportives", "parent": "R"} +{"index": {"_id": "93.12Z"}} +{"name": "Activités de clubs de sports", "parent": "R"} +{"index": {"_id": "93.13Z"}} +{"name": "Activités des centres de culture physique", "parent": "R"} +{"index": {"_id": "93.19Z"}} +{"name": "Autres activités liées au sport", "parent": "R"} +{"index": {"_id": "93.21Z"}} +{"name": "Activités des parcs d'attractions et parcs à thèmes", "parent": "R"} +{"index": {"_id": "93.29Z"}} +{"name": "Autres activités récréatives et de loisirs", "parent": "R"} +{"index": {"_id": "S"}} +{"name": "Autres activités de services", "parent": null} +{"index": {"_id": "94.11Z"}} +{"name": "Activités des organisations patronales et consulaires", "parent": "S"} +{"index": {"_id": "94.12Z"}} +{"name": "Activités des organisations professionnelles", "parent": "S"} +{"index": {"_id": "94.20Z"}} +{"name": "Activités des syndicats de salariés", "parent": "S"} +{"index": {"_id": "94.91Z"}} +{"name": "Activités des organisations religieuses", "parent": "S"} +{"index": {"_id": "94.92Z"}} +{"name": "Activités des organisations politiques", "parent": "S"} +{"index": {"_id": "94.99Z"}} +{"name": "Autres organisations fonctionnant par adhésion volontaire", "parent": "S"} +{"index": {"_id": "95.11Z"}} +{"name": "Réparation d'ordinateurs et d'équipements périphériques", "parent": "S"} +{"index": {"_id": "95.12Z"}} +{"name": "Réparation d'équipements de communication", "parent": "S"} +{"index": {"_id": "95.21Z"}} +{"name": "Réparation de produits électroniques grand public", "parent": "S"} +{"index": {"_id": "95.22Z"}} +{"name": "Réparation d'appareils électroménagers et d'équipements pour la maison et le jardin", "parent": "S"} +{"index": {"_id": "95.23Z"}} +{"name": "Réparation de chaussures et d'articles en cuir", "parent": "S"} +{"index": {"_id": "95.24Z"}} +{"name": "Réparation de meubles et d'équipements du foyer", "parent": "S"} +{"index": {"_id": "95.25Z"}} +{"name": "Réparation d'articles d'horlogerie et de bijouterie", "parent": "S"} +{"index": {"_id": "95.29Z"}} +{"name": "Réparation d'autres biens personnels et domestiques", "parent": "S"} +{"index": {"_id": "96.01A"}} +{"name": "Blanchisserie-teinturerie de gros", "parent": "S"} +{"index": {"_id": "96.01B"}} +{"name": "Blanchisserie-teinturerie de détail", "parent": "S"} +{"index": {"_id": "96.02A"}} +{"name": "Coiffure", "parent": "S"} +{"index": {"_id": "96.02B"}} +{"name": "Soins de beauté", "parent": "S"} +{"index": {"_id": "96.03Z"}} +{"name": "Services funéraires", "parent": "S"} +{"index": {"_id": "96.04Z"}} +{"name": "Entretien corporel", "parent": "S"} +{"index": {"_id": "96.09Z"}} +{"name": "Autres services personnels n.c.a.", "parent": "S"} +{"index": {"_id": "T"}} +{"name": "Activités des ménages en tant qu'employeurs ; activités indifférenciées des ménages en tant que producteurs de biens et services pour usage propre", "parent": null} +{"index": {"_id": "97.00Z"}} +{"name": "Activités des ménages en tant qu'employeurs de personnel domestique", "parent": "T"} +{"index": {"_id": "98.10Z"}} +{"name": "Activités indifférenciées des ménages en tant que producteurs de biens pour usage propre", "parent": "T"} +{"index": {"_id": "98.20Z"}} +{"name": "Activités indifférenciées des ménages en tant que producteurs de services pour usage propre", "parent": "T"} +{"index": {"_id": "U"}} +{"name": "Activités extra-territoriales", "parent": null} +{"index": {"_id": "99.00Z"}} +{"name": "Activités des organisations et organismes extraterritoriaux", "parent": "U"} diff --git a/duniter4j-es-gchange/src/test/es-home/config/elasticsearch.yml b/duniter4j-es-gchange/src/test/es-home/config/elasticsearch.yml new file mode 100644 index 00000000..828ddbd0 --- /dev/null +++ b/duniter4j-es-gchange/src/test/es-home/config/elasticsearch.yml @@ -0,0 +1,178 @@ +# ======================== Elasticsearch Configuration ========================= +# +# NOTE: Elasticsearch comes with reasonable defaults for most settings. +# Before you set out to tweak and tune the configuration, make sure you +# understand what are you trying to accomplish and the consequences. +# +# The primary way of configuring a node is via this file. This template lists +# the most important settings you may want to configure for a production cluster. +# +# Please see the documentation for further information on configuration options: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration.html> +# +# ---------------------------------- Cluster ----------------------------------- +# +# Use a descriptive name for your cluster: +# +# cluster.name: my-application +cluster.name: duniter4j-elasticsearch-TEST +# +# ------------------------------------ Node ------------------------------------ +# +# Use a descriptive name for the node: +# +# node.name: node-1 +# +# Add custom attributes to the node: +# +# node.rack: r1 +# +# ----------------------------------- Paths ------------------------------------ +# +# Path to directory where to store the data (separate multiple locations by comma): +# +# path.data: /path/to/data +# +# Path to log files: +# +# path.logs: /path/to/logs +# +# ----------------------------------- Memory ----------------------------------- +# +# Lock the memory on startup: +# +# bootstrap.mlockall: true +# +# Make sure that the `ES_HEAP_SIZE` environment variable is set to about half the memory +# available on the system and that the owner of the process is allowed to use this limit. +# +# Elasticsearch performs poorly when the system is swapping the memory. +# +# ---------------------------------- Network ----------------------------------- +# +# Set the bind address to a specific IP (IPv4 or IPv6): +# +# network.host: 192.168.233.1 +# +# Set a custom port for HTTP: +# +# http.port: 9200-9300 + +http.cors.allow-origin: "/.*/" +http.cors.enabled: true + +# Internal transport layer +# +# transport.tcp.port: 9210-9220 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-network.html> +# +# --------------------------------- Discovery ---------------------------------- +# +# Pass an initial list of hosts to perform discovery when new node is started: +# The default list of hosts is ["127.0.0.1", "[::1]"] +# +# discovery.zen.ping.unicast.hosts: ["host1", "host2"] +#discovery.zen.ping.unicast.hosts: ["127.0.0.1", ""] +# +# Prevent the "split brain" by configuring the majority of nodes (total number of nodes / 2 + 1): +# +# discovery.zen.minimum_master_nodes: 3 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-discovery.html> +# +# ---------------------------------- Gateway ----------------------------------- +# +# Block initial recovery after a full cluster restart until N nodes are started: +# +# gateway.recover_after_nodes: 3 +# +# For more information, see the documentation at: +# <http://www.elastic.co/guide/en/elasticsearch/reference/current/modules-gateway.html> +# +# ---------------------------------- Various ----------------------------------- +# +# Disable starting multiple nodes on a single system: +# +# node.max_local_storage_nodes: 1 +# +# Require explicit names when deleting indices: +# +# rest.destructive_requires_name: true + +security.manager.enabled: false + +# +# ---------------------------------- Duniter4j --------------------------------- +# +# Disbale duniter4j plugin +# +# duniter.enabled: false +# +# Reset and reload all Duniter4j data at startup - DO SET to true in production +# +# duniter.indices.reload: true +# +# Default string analyzer +# +duniter.string.analyzer: french +# +# Enabling node blockchain synchronization +# +duniter.blockchain.sync.enable: false +# +# Duniter node to synchronize +# +duniter.host: cgeek.fr +duniter.port: 9330 +# +# ---------------------------------- Duniter4j security ------------------------- +# +duniter.keyring.salt: abc +duniter.keyring.password: def + +# Enable security, to disable HTTP access to the default ES admin API +# +duniter.security.enable: false +# +# Security token prefix (default: 'duniter-') +# +# duniter.auth.token.prefix: duniter- +# +# Token validity duration, in seconds (default: 600) +# +# duniter.auth.tokenValidityDuration: 3600 # = 1hour +# +# ---------------------------------- Duniter4j P2P sync ------------------------- +# +# Should synchronize data using P2P +# +duniter.data.sync.enable: false +#duniter.data.sync.host: data.duniter.fr +#duniter.data.sync.port: 80 + +# ---------------------------------- Duniter4j SMTP server ------------------------- +# +# SMTP server configuration (host and port) +# +#duniter.mail.smtp.host: localhost +#duniter.mail.smtp.port: 25 +# +# Mail 'from' address +# +#duniter.mail.from: no-reply@domain.com +duniter.mail.from: root@EIS-DEV +# +# Mail: admin address +# +#duniter.mail.admin: user@domain.com +duniter.mail.admin: blavenie@EIS-DEV +# +# Mail subject prefix +# +#duniter.mail.subject.prefix: [Duniter4j ES] + +duniter.changes.listenSource: */block +duniter.ws.port: 9400 diff --git a/duniter4j-es-gchange/src/test/es-home/config/logging.yml b/duniter4j-es-gchange/src/test/es-home/config/logging.yml new file mode 100644 index 00000000..15cfa3e1 --- /dev/null +++ b/duniter4j-es-gchange/src/test/es-home/config/logging.yml @@ -0,0 +1,97 @@ +# you can override this using by setting a system property, for example -Des.logger.level=DEBUG +es.logger.level: INFO +rootLogger: ${es.logger.level}, console, file +logger: + # log rest execution errors for easier debugging + action: DEBUG + + # deprecation logging, turn to DEBUG to see them + deprecation: INFO, deprecation_log_file + + # reduce the logging for aws, too much is logged under the default INFO + com.amazonaws: WARN + # aws will try to do some sketchy JMX stuff, but its not needed. + com.amazonaws.jmx.SdkMBeanRegistrySupport: ERROR + com.amazonaws.metrics.AwsSdkMetrics: ERROR + + org.apache.http: INFO + + org.duniter: INFO + + org.duniter.elasticsearch: DEBUG + + duniter : DEBUG + duniter.network.p2p: TRACE + + security: DEBUG + + org.nuiton.i18n: WARN + org.nuiton.config: WARN + + # gateway + #gateway: DEBUG + #index.gateway: DEBUG + + # peer shard recovery + #indices.recovery: DEBUG + + # discovery + #discovery: TRACE + + index.search.slowlog: TRACE, index_search_slow_log_file + index.indexing.slowlog: TRACE, index_indexing_slow_log_file + +additivity: + index.search.slowlog: false + index.indexing.slowlog: false + deprecation: false + +appender: + console: + type: console + layout: + type: consolePattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %.10000m%n" + + # Use the following log4j-extras RollingFileAppender to enable gzip compression of log files. + # For more information see https://logging.apache.org/log4j/extras/apidocs/org/apache/log4j/rolling/RollingFileAppender.html + #file: + #type: extrasRollingFile + #file: ${path.logs}/${cluster.name}.log + #rollingPolicy: timeBased + #rollingPolicy.FileNamePattern: ${path.logs}/${cluster.name}.log.%d{yyyy-MM-dd}.gz + #layout: + #type: pattern + #conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + deprecation_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_deprecation.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + index_search_slow_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_index_search_slowlog.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" + + index_indexing_slow_log_file: + type: dailyRollingFile + file: ${path.logs}/${cluster.name}_index_indexing_slowlog.log + datePattern: "'.'yyyy-MM-dd" + layout: + type: pattern + conversionPattern: "[%d{ISO8601}][%-5p][%-25c] %m%n" diff --git a/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestFixtures.java b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestFixtures.java new file mode 100644 index 00000000..36f09330 --- /dev/null +++ b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestFixtures.java @@ -0,0 +1,28 @@ +package org.duniter.elasticsearch; + +/* + * #%L + * UCoin Java Client :: ElasticSearch Indexer + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +public class TestFixtures extends org.duniter.core.test.TestFixtures { + +} diff --git a/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestResource.java b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestResource.java new file mode 100644 index 00000000..2b27a3e5 --- /dev/null +++ b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/TestResource.java @@ -0,0 +1,141 @@ +package org.duniter.elasticsearch; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import com.google.common.collect.Lists; +import org.duniter.core.client.config.ConfigurationOption; +import org.duniter.core.client.service.ServiceLocator; +import org.apache.commons.io.FileUtils; +import org.junit.runner.Description; +import org.nuiton.i18n.I18n; +import org.nuiton.i18n.init.DefaultI18nInitializer; +import org.nuiton.i18n.init.UserI18nInitializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +public class TestResource extends org.duniter.core.test.TestResource { + + private static final Logger log = LoggerFactory.getLogger(TestResource.class); + + public static TestResource create() { + return new TestResource(null); + } + + public static TestResource create(String configName) { + return new TestResource(configName); + } + + private TestFixtures fixtures = new TestFixtures(); + + protected TestResource(String configName) { + super(configName); + } + + public TestFixtures getFixtures() { + return fixtures; + } + + protected void before(Description description) throws Throwable { + super.before(description); + + // Initialize configuration + initConfiguration(getConfigFileName()); + + // Init i18n + initI18n(); + + // Initialize service locator + ServiceLocator.instance().init(); + } + + /** + * Return configuration files prefix (i.e. 'allegro-test') + * Could be override by external project + * + * @return the prefix to use to retrieve configuration files + */ + protected String getConfigFilesPrefix() { + return "duniter4j-elasticsearch-test"; + } + + protected String getI18nBundleName() { + return "duniter4j-elasticsearch-i18n"; + } + + /* -- -- */ + + /** + * Convenience methods that could be override to initialize other configuration + * + * @param configFilename + * @param configArgs + */ + protected void initConfiguration(String configFilename) { + String[] configArgs = getConfigArgs(); + //PluginSettings config = new PluginSettings(configFilename, configArgs); + //PluginSettings.setInstance(config); + } + + protected void initI18n() throws IOException { + /*PluginSettings config ;//= PluginSettings.instance(); + + // --------------------------------------------------------------------// + // init i18n + // --------------------------------------------------------------------// + File i18nDirectory = new File(config.getDataDirectory(), "i18n"); + if (i18nDirectory.exists()) { + // clean i18n cache + FileUtils.cleanDirectory(i18nDirectory); + } + + FileUtils.forceMkdir(i18nDirectory); + + if (log.isDebugEnabled()) { + log.debug("I18N directory: " + i18nDirectory); + } + + Locale i18nLocale = config.getI18nLocale(); + + if (log.isInfoEnabled()) { + log.info(String.format("Starts i18n with locale [%s] at [%s]", + i18nLocale, i18nDirectory)); + } + I18n.init(new UserI18nInitializer( + i18nDirectory, new DefaultI18nInitializer(getI18nBundleName())), + i18nLocale);*/ + } + + protected String[] getConfigArgs() { + List<String> configArgs = Lists.newArrayList(); + configArgs.addAll(Lists.newArrayList( + "--option", ConfigurationOption.BASEDIR.getKey(), getResourceDirectory().getAbsolutePath())); + return configArgs.toArray(new String[configArgs.size()]); + } + +} diff --git a/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java new file mode 100644 index 00000000..a305ac1b --- /dev/null +++ b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java @@ -0,0 +1,172 @@ +package org.duniter.elasticsearch.service; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import org.duniter.core.client.config.Configuration; +import org.duniter.core.client.model.bma.BlockchainBlock; +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.client.service.bma.BlockchainRemoteService; +import org.duniter.elasticsearch.TestResource; +import org.junit.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +@Ignore +public class BlockchainServiceTest { + + private static final Logger log = LoggerFactory.getLogger(BlockchainServiceTest.class); + + @ClassRule + public static final TestResource resource = TestResource.create(); + + private BlockchainService service; + private BlockchainRemoteService blockchainRemoteService; + private Configuration config; + private Peer peer; + + @Before + public void setUp() throws Exception { + //service = ServiceLocator.instance().getBlockIndexerService(); + //blockchainRemoteService = ServiceLocator.instance().getBlockchainRemoteService(); + config = Configuration.instance(); + peer = createTestPeer(); + + initLocalNode(); + } + + @Test + public void createIndex() throws Exception { + String currencyName = resource.getFixtures().getCurrency(); + + // drop and recreate index + service.deleteIndex(currencyName); + + service.createIndex(currencyName); + } + + + @Test + public void indexBlock() throws Exception { + // Read a block + BlockchainBlock currentBlock = blockchainRemoteService.getCurrentBlock(peer); + + // Create a new non-existing block + service.indexBlock(currentBlock, true); + + // Update a existing block + { + currentBlock.setMembersCount(1000000); + + service.indexBlock(currentBlock, true); + } + } + + @Test + public void indexCurrentBlock() throws Exception { + // Create a block with a fake hash + BlockchainBlock aBlock = blockchainRemoteService.getBlock(peer, 8450); + service.indexCurrentBlock(aBlock, true); + } + + @Test + // FIXME make this works + @Ignore + public void searchBlocks() throws Exception { + String currencyName = resource.getFixtures().getCurrency(); + + // Create a block with a fake hash + BlockchainBlock aBlock = blockchainRemoteService.getCurrentBlock(peer); + aBlock.setHash("myUnitTestHash"); + service.saveBlock(aBlock, true, true); + + Thread.sleep(5 * 1000); // wait 5s that ES process the block + + // match multi words + String queryText = aBlock.getHash(); + List<BlockchainBlock> blocks = service.findBlocksByHash(currencyName, queryText); + //assertResults(queryText, blocks); + + Thread.sleep(5 * 1000); // wait 5s that ES process the block + + BlockchainBlock loadBlock = service.getBlockById(currencyName, aBlock.getNumber()); + Assert.assertNotNull(loadBlock); + Assert.assertEquals(aBlock.getHash(), loadBlock.getHash()); + } + + @Test + public void getMaxBlockNumber() throws Exception { + String currencyName = resource.getFixtures().getCurrency(); + + // match multi words + Integer maxBlockNumber = service.getMaxBlockNumber(currencyName); + Assert.assertNotNull(maxBlockNumber); + } + + + @Test + @Ignore + public void allInOne() throws Exception { + + createIndex(); + indexBlock(); + searchBlocks(); + } + + /* -- internal methods */ + + protected void initLocalNode() throws Exception { + String currencyName = resource.getFixtures().getCurrency(); + + // Make sure the index exists + service.deleteIndex(currencyName); + service.createIndex(currencyName); + + // Get the first block from peer + BlockchainBlock firstBlock = blockchainRemoteService.getBlock(peer, 0); + + // Make sure the block has been indexed + service.indexBlock(firstBlock, true); + + } + + protected void assertResults(String queryText, List<BlockchainBlock> result) { + log.info(String.format("Results for a search on [%s]", queryText)); + Assert.assertNotNull(result); + Assert.assertTrue(result.size() > 0); + for (BlockchainBlock block: result) { + log.info(" - " + block.getNumber()); + } + } + + protected Peer createTestPeer() { + Peer peer = new Peer( + Configuration.instance().getNodeHost(), + Configuration.instance().getNodePort()); + + return peer; + } + +} diff --git a/duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java similarity index 96% rename from duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java rename to duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java index adc4a46c..4987281d 100644 --- a/duniter4j-elasticsearch/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java +++ b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/RegistryRecordIndexerServiceTest.java @@ -23,6 +23,7 @@ package org.duniter.elasticsearch.service; */ import org.duniter.elasticsearch.TestResource; +import org.duniter.elasticsearch.gchange.service.RegistryService; import org.junit.Before; import org.junit.ClassRule; import org.junit.Ignore; diff --git a/duniter4j-es-gchange/src/test/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-es-gchange/src/test/resources/META-INF/services/org.duniter.core.beans.Bean new file mode 100644 index 00000000..1d327a74 --- /dev/null +++ b/duniter4j-es-gchange/src/test/resources/META-INF/services/org.duniter.core.beans.Bean @@ -0,0 +1,14 @@ +org.duniter.core.client.service.bma.BlockchainRemoteServiceImpl +org.duniter.core.client.service.bma.NetworkRemoteServiceImpl +org.duniter.core.client.service.bma.WotRemoteServiceImpl +org.duniter.core.client.service.bma.TransactionRemoteServiceImpl +org.duniter.core.service.Ed25519CryptoServiceImpl +org.duniter.core.service.MailServiceImpl +org.duniter.core.client.service.HttpServiceImpl +org.duniter.core.client.service.DataContext +org.duniter.core.client.service.local.PeerServiceImpl +org.duniter.core.client.service.local.CurrencyServiceImpl +org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl +org.duniter.core.client.dao.mem.MemoryPeerDaoImpl +org.duniter.elasticsearch.service.ElasticSearchService +org.duniter.elasticsearch.service.registry.CurrencyRegistryService \ No newline at end of file diff --git a/duniter4j-es-gchange/src/test/resources/curl_test.sh b/duniter4j-es-gchange/src/test/resources/curl_test.sh new file mode 100755 index 00000000..4f62377a --- /dev/null +++ b/duniter4j-es-gchange/src/test/resources/curl_test.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +curl -XPOST "http://data.duniter.fr/market/comment/_search?pretty" -d' +{ + "query": { + "bool":{ + "filter": [ + {"term":{ + "record":"AVbieTIAup9uzWgKipsC" + } + } + ] + } + } +}' + diff --git a/duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-localhost-node.properties b/duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-localhost-node.properties new file mode 100644 index 00000000..38d7a5d9 --- /dev/null +++ b/duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-localhost-node.properties @@ -0,0 +1,12 @@ +duniter4j.node.host=metab.ucoin.fr +duniter4j.node.port=9201 + +duniter4j.elasticsearch.embedded.enable=false +duniter4j.elasticsearch.local=fals +duniter4j.elasticsearch.http.enable=false +duniter4j.elasticsearch.cluster.name=duniter4j-elacticsearch-test + +#duniter4j.elasticsearch.cluster.name=duniter4j-elacticsearch + +duniter4j.elasticsearch.host=localhost +duniter4j.elasticsearch.port=9300 diff --git a/duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-test.properties b/duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-test.properties new file mode 100644 index 00000000..27e326f1 --- /dev/null +++ b/duniter4j-es-gchange/src/test/resources/duniter4j-elasticsearch-test.properties @@ -0,0 +1,16 @@ +duniter4j.node.host=metab.ucoin.fr +duniter4j.node.port=9201 + +duniter4j.basedir=target/es-home + +#duniter4j.elasticsearch.data +#duniter4j.elasticsearch.embedded.enable=true +duniter4j.elasticsearch.local=false +duniter4j.elasticsearch.http.enable=true +duniter4j.elasticsearch.cluster.name=duniter4j-elasticsearch + +#duniter4j.elasticsearch.cluster.name=duniter4j-elacticsearch + +duniter4j.elasticsearch.embedded.enable=false +duniter4j.elasticsearch.host=192.168.0.5 +duniter4j.elasticsearch.port=9300 diff --git a/duniter4j-es-gchange/src/test/resources/log4j.properties b/duniter4j-es-gchange/src/test/resources/log4j.properties new file mode 100644 index 00000000..2712b72e --- /dev/null +++ b/duniter4j-es-gchange/src/test/resources/log4j.properties @@ -0,0 +1,18 @@ +### +# Global logging configuration +log4j.rootLogger=ERROR, stdout + +# Console output +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - [%t] %m%n + +# duniter4j levels +log4j.logger.org.duniter=INFO +#log4j.logger.org.duniter=DEBUG +log4j.logger.org.duniter.core=WARN +log4j.logger.org.duniter.elasticsearch=DEBUG + +# Other frameworks levels +log4j.logger.org.elasticsearch=INFO + diff --git a/duniter4j-es-gchange/src/test/resources/registry-test-records.json b/duniter4j-es-gchange/src/test/resources/registry-test-records.json new file mode 100644 index 00000000..c9c11c01 --- /dev/null +++ b/duniter4j-es-gchange/src/test/resources/registry-test-records.json @@ -0,0 +1,23 @@ +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ +{"index": {"_id": "AVOt3cmVo7-63byx1Jow"}} +{"title": "Benoit Lavenier (kimamila)", "description": "Pasionné de plein de trucs...", "category": "particulier", "location":"Martigné-sur-Mayenne", "pictures": [{"src": "data:image/jpeg;base64,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"}], "issuer": "G2CBgZBPLe6FSFUgpx2Jf1Aqsgta6iib3vmDRA1yLiqU", "hash": "7zPfFwyXGZKYMmLTzVynFGVEwLDfj48a5E3EA2RWtifq", "signature": "CP4p0+Nby/Fs7exp/mlLnOAu8iMJwyLRW6UHkZXZX8q8WbqxHYBRo2uzpcFAT0zEYSig7j3HqYdeoA3MpU1BCQ=="} diff --git a/duniter4j-es-user/pom.xml b/duniter4j-es-user/pom.xml new file mode 100644 index 00000000..d4f1f565 --- /dev/null +++ b/duniter4j-es-user/pom.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>duniter4j</artifactId> + <groupId>org.duniter</groupId> + <version>0.3.5-SNAPSHOT</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>duniter4j-es-user</artifactId> + <packaging>jar</packaging> + <name>Duniter4j :: ElasticSearch User plugin</name> + + <dependencies> + <dependency> + <groupId>org.duniter</groupId> + <artifactId>duniter4j-es-core</artifactId> + <version>${project.version}</version> + </dependency> + + <!-- Elastic Search --> + <dependency> + <groupId>org.elasticsearch</groupId> + <artifactId>elasticsearch</artifactId> + <scope>provided</scope> + </dependency> + </dependencies> + + <build> + <resources> + <resource> + <directory>src/main/filtered-resources</directory> + <filtering>true</filtering> + <includes> + <include>*.config</include> + <include>**/*.properties</include> + </includes> + </resource> + <resource> + <directory>src/main/resources</directory> + <filtering>false</filtering> + </resource> + </resources> + + <plugins> + <plugin> + <groupId>org.nuiton.i18n</groupId> + <artifactId>i18n-maven-plugin</artifactId> + + <executions> + <execution> + <id>scan-sources</id> + <configuration> + <entries> + <entry> + <specificGoal>parserValidation</specificGoal> + <basedir>${maven.src.dir}/main/java/</basedir> + <includes> + <param>**/**-validation.xml</param> + </includes> + </entry> + </entries> + </configuration> + <goals> + <goal>parserJava</goal> + <goal>parserValidation</goal> + <goal>gen</goal> + </goals> + </execution> + <execution> + <id>make-bundle</id> + <goals> + <goal>bundle</goal> + </goals> + </execution> + </executions> + </plugin> + + <plugin> + <artifactId>maven-assembly-plugin</artifactId> + <executions> + <execution> + <id>assembly-plugin</id> + <phase>package</phase> + <goals> + <goal>single</goal> + </goals> + <configuration> + <attach>true</attach> + <appendAssemblyId>false</appendAssemblyId> + <finalName>${bundlePrefix}</finalName> + <descriptors> + <descriptor> + ${basedir}/src/main/assembly/plugin.xml + </descriptor> + </descriptors> + <skipAssembly>${assembly.skip}</skipAssembly> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> +</project> \ No newline at end of file diff --git a/duniter4j-es-user/src/main/assembly/plugin.xml b/duniter4j-es-user/src/main/assembly/plugin.xml new file mode 100644 index 00000000..141f9bb5 --- /dev/null +++ b/duniter4j-es-user/src/main/assembly/plugin.xml @@ -0,0 +1,43 @@ +<?xml version="1.0"?> +<assembly> + <id>plugin</id> + + + <formats> + <format>zip</format> + </formats> + + <includeBaseDirectory>false</includeBaseDirectory> + + <dependencySets> + <dependencySet> + <outputDirectory>/</outputDirectory> + <useProjectArtifact>true</useProjectArtifact> + <useTransitiveFiltering>true</useTransitiveFiltering> + <excludes> + <exclude>org.duniter:duniter4j-es-core</exclude> + <exclude>org.elasticsearch:elasticsearch</exclude> + <exclude>net.java.dev.jna:jna</exclude> + <exclude>com.fasterxml.jackson.core:jackson-core</exclude> + <exclude>log4j:log4j</exclude> + </excludes> + </dependencySet> + </dependencySets> + + <fileSets> + <fileSet> + <includes> + <include>LICENSE</include> + </includes> + </fileSet> + + <fileSet> + <directory>target/classes</directory> + <outputDirectory/> + <includes> + <include>plugin-descriptor.properties</include> + <include>plugin-security.policy</include> + </includes> + </fileSet> + </fileSets> +</assembly> \ No newline at end of file diff --git a/duniter4j-es-user/src/main/filtered-resources/log4j.properties b/duniter4j-es-user/src/main/filtered-resources/log4j.properties new file mode 100644 index 00000000..7b6667b1 --- /dev/null +++ b/duniter4j-es-user/src/main/filtered-resources/log4j.properties @@ -0,0 +1,32 @@ + +# Global logging configuration +log4j.rootLogger=ERROR, stdout, file +#log4j.rootLogger=ERROR, stdout + +# Console output +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p %m%n + +# Duniter4j levels +log4j.logger.org.duniter=INFO +#log4j.logger.org.duniter.core.client=DEBUG +#log4j.logger.org.duniter.core.client.service=DEBUG +log4j.logger.org.duniter.elasticsearch=DEBUG + +# Other frameworks levels +log4j.logger.org.nuiton.util=WARN +log4j.logger.org.nuiton.config=WARN +log4j.logger.org.nuiton.converter=WARN +log4j.logger.org.nuiton.i18n=ERROR +log4j.logger.org.elasticsearch=WARN +#log4j.logger.org.elasticsearch=INFO + +log4j.appender.file=org.apache.log4j.RollingFileAppender +log4j.appender.file.file=${duniter4j.log.file} +log4j.appender.file.MaxFileSize=10MB +log4j.appender.file.MaxBackupIndex=4 + +log4j.appender.file.layout=org.apache.log4j.PatternLayout +log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - [%t] %m%n + diff --git a/duniter4j-es-user/src/main/filtered-resources/plugin-descriptor.properties b/duniter4j-es-user/src/main/filtered-resources/plugin-descriptor.properties new file mode 100644 index 00000000..785b7ddf --- /dev/null +++ b/duniter4j-es-user/src/main/filtered-resources/plugin-descriptor.properties @@ -0,0 +1,9 @@ +name=duniter4j-es-user +description=Plugin for Duniter User API +version=${project.version} +site=false +jvm=true +classname=org.duniter.elasticsearch.user.Plugin +java.version=1.7 +elasticsearch.version=2.3.3 +isolated=true diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java new file mode 100644 index 00000000..f1bde795 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/Plugin.java @@ -0,0 +1,86 @@ +package org.duniter.elasticsearch.user; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import com.google.common.collect.Lists; +import org.duniter.elasticsearch.PluginInit; +import org.duniter.elasticsearch.user.service.ServiceModule; +import org.duniter.elasticsearch.user.rest.RestModule; +import org.elasticsearch.common.component.LifecycleComponent; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.inject.Module; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.Settings; + +import java.util.Collection; + +public class Plugin extends org.elasticsearch.plugins.Plugin { + + private ESLogger log = ESLoggerFactory.getLogger(Plugin.class.getName()); + + private boolean enable; + + @Inject public Plugin(Settings settings) { + this.enable = settings.getAsBoolean("duniter.user.enabled", true); + } + + @Override + public String name() { + return "duniter.user"; + } + + @Override + public String description() { + return "Duniter ElasticSearch User Plugin"; + } + + @Override + public Collection<Module> nodeModules() { + Collection<Module> modules = Lists.newArrayList(); + if (!enable) { + log.warn(description() + " has been disabled."); + return modules; + } + + modules.add(new RestModule()); + modules.add(new ServiceModule()); + + return modules; + } + + @Override + public Collection<Class<? extends LifecycleComponent>> nodeServices() { + Collection<Class<? extends LifecycleComponent>> components = Lists.newArrayList(); + if (!enable) { + return components; + } + components.add(PluginSettings.class); + components.add(PluginInit.class); + return components; + } + + /* -- protected methods -- */ + + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java similarity index 57% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java rename to duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java index 1f6d9019..87f044bf 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.node; +package org.duniter.elasticsearch.user; /* * #%L @@ -22,17 +22,13 @@ package org.duniter.elasticsearch.node; * #L% */ -import org.duniter.core.client.model.elasticsearch.Currency; -import org.duniter.core.client.model.local.Peer; import org.duniter.elasticsearch.PluginSettings; -import org.duniter.elasticsearch.action.security.RestSecurityController; -import org.duniter.elasticsearch.service.*; -import org.duniter.elasticsearch.service.event.Event; -import org.duniter.elasticsearch.service.event.EventCodes; -import org.duniter.elasticsearch.service.event.EventService; -import org.duniter.elasticsearch.service.synchro.SynchroService; import org.duniter.elasticsearch.threadpool.ThreadPool; -import org.elasticsearch.client.Client; +import org.duniter.elasticsearch.user.service.HistoryService; +import org.duniter.elasticsearch.user.service.MessageService; +import org.duniter.elasticsearch.user.service.SynchroService; +import org.duniter.elasticsearch.user.service.UserService; +import org.duniter.elasticsearch.user.service.event.UserEventService; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; @@ -40,28 +36,23 @@ import org.elasticsearch.common.inject.Injector; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.logging.Loggers; import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.rest.RestRequest; /** * Created by blavenie on 17/06/16. */ -public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { +public class PluginInit extends AbstractLifecycleComponent<org.duniter.elasticsearch.PluginInit> { private final PluginSettings pluginSettings; private final ThreadPool threadPool; private final Injector injector; private final static ESLogger logger = Loggers.getLogger("node"); - private final Client client; - private final String clusterName; @Inject - public DuniterNode(Client client, Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) { + public PluginInit(Settings settings, PluginSettings pluginSettings, ThreadPool threadPool, final Injector injector) { super(settings); this.pluginSettings = pluginSettings; this.threadPool = threadPool; this.injector = injector; - this.client = client; - this.clusterName = settings.get("cluster.name"); } @Override @@ -75,15 +66,6 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN); }, ClusterHealthStatus.YELLOW, ClusterHealthStatus.GREEN); - // When started - threadPool.scheduleOnStarted(() -> { - // Notify admin - injector.getInstance(EventService.class) - .notifyAdmin(new Event( - Event.EventType.INFO, - EventCodes.NODE_STARTED.name(), - new String[]{clusterName})); - }); } @Override @@ -102,27 +84,18 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { if (reloadIndices) { if (logger.isInfoEnabled()) { - logger.info("Reloading all Duniter indices..."); + logger.info("Reloading all User indices..."); } - injector.getInstance(RegistryService.class) - .deleteIndex() - .createIndexIfNotExists(); - injector.getInstance(MarketService.class) + injector.getInstance(HistoryService.class) .deleteIndex() .createIndexIfNotExists(); injector.getInstance(MessageService.class) .deleteIndex() .createIndexIfNotExists(); - injector.getInstance(UserService.class) .deleteIndex() .createIndexIfNotExists(); - - injector.getInstance(HistoryService.class) - .deleteIndex() - .createIndexIfNotExists(); - - injector.getInstance(EventService.class) + injector.getInstance(UserEventService.class) .deleteIndex() .createIndexIfNotExists(); @@ -135,12 +108,10 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { if (logger.isInfoEnabled()) { logger.info("Checking Duniter indices..."); } - injector.getInstance(RegistryService.class).createIndexIfNotExists(); - injector.getInstance(MarketService.class).createIndexIfNotExists(); - injector.getInstance(MessageService.class).createIndexIfNotExists(); - injector.getInstance(UserService.class).createIndexIfNotExists(); injector.getInstance(HistoryService.class).createIndexIfNotExists(); - injector.getInstance(EventService.class).createIndexIfNotExists(); + injector.getInstance(UserService.class).createIndexIfNotExists(); + injector.getInstance(MessageService.class).createIndexIfNotExists(); + injector.getInstance(UserEventService.class).createIndexIfNotExists(); if (logger.isInfoEnabled()) { logger.info("Checking Duniter indices... [OK]"); @@ -149,24 +120,6 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { } protected void synchronize() { - if (pluginSettings.enableBlockchainSync()) { - - Peer peer = pluginSettings.checkAndGetPeer(); - - // Index (or refresh) node's currency - Currency currency = injector.getInstance(RegistryService.class).indexCurrencyFromPeer(peer, true); - - // Add access to currency index - injector.getInstance(RestSecurityController.class).allowIndexType(RestRequest.Method.GET, - currency.getCurrencyName(), - BlockchainService.BLOCK_TYPE); - - // Index blocks (and listen if new block appear) - injector.getInstance(BlockchainService.class) - .indexLastBlocks(peer) - .listenAndIndexNewBlock(peer); - } - if (pluginSettings.enableDataSync()) { // Synchronize injector.getInstance(SynchroService.class).synchronize(); diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java new file mode 100644 index 00000000..2f6e1077 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginSettings.java @@ -0,0 +1,104 @@ +package org.duniter.elasticsearch.user; + +/* + * #%L + * UCoin Java Client :: Core API + * %% + * Copyright (C) 2014 - 2015 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + + +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; + +/** + * Access to configuration options + * @author Benoit Lavenier <benoit.lavenier@e-is.pro> + * @since 1.0 + */ +public class PluginSettings extends org.duniter.elasticsearch.PluginSettings { + + @Inject + public PluginSettings(Settings settings) { + super(settings); + } + + public String getDefaultStringAnalyzer() { + return settings.get("duniter.string.analyzer", "english"); + } + + public String getKeyringSalt() { + return settings.get("duniter.keyring.salt"); + } + + public String getKeyringPassword() { + return settings.get("duniter.keyring.password"); + } + + public String getKeyringPublicKey() { + return settings.get("duniter.keyring.pub"); + } + + public String getKeyringSecretKey() { + return settings.get("duniter.keyring.sec"); + } + + public boolean enableDataSync() { + return settings.getAsBoolean("duniter.user.sync.enable", false); + } + + public String getDataSyncHost() { + return settings.get("duniter.user.sync.host", "data.duniter.fr"); + } + + public int getDataSyncPort() { + return settings.getAsInt("duniter.user.sync.port", 80); + } + + public String getMailSmtpHost() { + return settings.get("duniter.mail.smtp.host", "localhost"); + } + + public int getMailSmtpPort() { + return settings.getAsInt("duniter.mail.smtp.port", 25); + } + + public String getMailSmtpUsername() { + return settings.get("duniter.mail.smtp.username"); + } + + public String getMailSmtpPassword() { + return settings.get("duniter.mail.smtp.password"); + } + + public String getMailAdmin() { + return settings.get("duniter.mail.admin"); + } + + public String getMailFrom() { + return settings.get("duniter.mail.from", "no-reply@duniter.fr"); + } + + public String getMailSubjectPrefix() { + return settings.get("duniter.mail.subject.prefix", "[Duniter4j ES]"); + } + + protected String getI18nBundleName() { + return "duniter4j-es-user-i18n"; + } +} diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/RestModule.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/RestModule.java new file mode 100644 index 00000000..bb044f17 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/RestModule.java @@ -0,0 +1,52 @@ +package org.duniter.elasticsearch.user.rest; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.user.rest.history.RestHistoryDeleteIndexAction; +import org.duniter.elasticsearch.user.rest.message.RestMessageInboxIndexAction; +import org.duniter.elasticsearch.user.rest.message.RestMessageOutboxIndexAction; +import org.duniter.elasticsearch.user.rest.user.RestUserProfileIndexAction; +import org.duniter.elasticsearch.user.rest.user.RestUserProfileUpdateAction; +import org.duniter.elasticsearch.user.rest.user.RestUserSettingsIndexAction; +import org.duniter.elasticsearch.user.rest.user.RestUserSettingsUpdateAction; +import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.Module; + +public class RestModule extends AbstractModule implements Module { + + @Override protected void configure() { + + // User + bind(RestUserProfileIndexAction.class).asEagerSingleton(); + bind(RestUserProfileUpdateAction.class).asEagerSingleton(); + bind(RestUserSettingsIndexAction.class).asEagerSingleton(); + bind(RestUserSettingsUpdateAction.class).asEagerSingleton(); + + // History + bind(RestHistoryDeleteIndexAction.class).asEagerSingleton(); + + // Message + bind(RestMessageInboxIndexAction.class).asEagerSingleton(); + bind(RestMessageOutboxIndexAction.class).asEagerSingleton(); + } +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/history/RestHistoryDeleteIndexAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/history/RestHistoryDeleteIndexAction.java new file mode 100644 index 00000000..8c7ddae5 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/history/RestHistoryDeleteIndexAction.java @@ -0,0 +1,45 @@ +package org.duniter.elasticsearch.user.rest.history; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.HistoryService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.logging.ESLogger; +import org.elasticsearch.common.logging.ESLoggerFactory; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestHistoryDeleteIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestHistoryDeleteIndexAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, HistoryService service) { + super(settings, controller, client, securityController, + HistoryService.INDEX, + HistoryService.DELETE_TYPE, + json -> service.indexDeleteFromJson(json)); + } +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageInboxIndexAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageInboxIndexAction.java new file mode 100644 index 00000000..c469cba9 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageInboxIndexAction.java @@ -0,0 +1,44 @@ +package org.duniter.elasticsearch.user.rest.message; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.MessageService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestMessageInboxIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestMessageInboxIndexAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + final MessageService service) { + super(settings, controller, client, securityController, + MessageService.INDEX, + MessageService.RECORD_TYPE, + json -> service.indexRecordFromJson(json)); + } +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageOutboxIndexAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageOutboxIndexAction.java new file mode 100644 index 00000000..e2ac8774 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/message/RestMessageOutboxIndexAction.java @@ -0,0 +1,44 @@ +package org.duniter.elasticsearch.user.rest.message; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.MessageService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestMessageOutboxIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestMessageOutboxIndexAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + final MessageService service) { + super(settings, controller, client, securityController, + MessageService.INDEX, + MessageService.OUTBOX_TYPE, + json -> service.indexRecordFromJson(json)); + } +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileIndexAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileIndexAction.java new file mode 100644 index 00000000..899db7e7 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileIndexAction.java @@ -0,0 +1,44 @@ +package org.duniter.elasticsearch.user.rest.user; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.UserService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestUserProfileIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestUserProfileIndexAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + UserService service) { + super(settings, controller, client, securityController, + UserService.INDEX, + UserService.PROFILE_TYPE, + json -> service.indexProfileFromJson(json)); + } +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileUpdateAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileUpdateAction.java new file mode 100644 index 00000000..b33b4f71 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserProfileUpdateAction.java @@ -0,0 +1,45 @@ +package org.duniter.elasticsearch.user.rest.user; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.UserService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestUserProfileUpdateAction extends AbstractRestPostUpdateAction { + + @Inject + public RestUserProfileUpdateAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + UserService service) { + super(settings, controller, client, securityController, + UserService.INDEX, + UserService.PROFILE_TYPE, + (json, id) -> service.updateProfileFromJson(json, id)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsIndexAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsIndexAction.java new file mode 100644 index 00000000..79371aa0 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsIndexAction.java @@ -0,0 +1,45 @@ +package org.duniter.elasticsearch.user.rest.user; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.UserService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestUserSettingsIndexAction extends AbstractRestPostIndexAction { + + @Inject + public RestUserSettingsIndexAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + final UserService service) { + super(settings, controller, client, securityController, + UserService.INDEX, + UserService.SETTINGS_TYPE, + json -> service.indexSettingsFromJson(json)); + } + +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsUpdateAction.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsUpdateAction.java new file mode 100644 index 00000000..24c8f5be --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/rest/user/RestUserSettingsUpdateAction.java @@ -0,0 +1,45 @@ +package org.duniter.elasticsearch.user.rest.user; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.rest.security.RestSecurityController; +import org.duniter.elasticsearch.user.service.UserService; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.rest.RestController; + +public class RestUserSettingsUpdateAction extends AbstractRestPostUpdateAction { + + @Inject + public RestUserSettingsUpdateAction(Settings settings, RestController controller, Client client, + RestSecurityController securityController, + final UserService service) { + super(settings, controller, client, securityController, + UserService.INDEX, + UserService.SETTINGS_TYPE, + (json, id) -> service.updateSettingsFromJson(json, id)); + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java similarity index 98% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java rename to duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java index fc3f0e43..a0005085 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service; +package org.duniter.elasticsearch.user.service; /* * #%L @@ -31,6 +31,7 @@ import org.duniter.core.exception.TechnicalException; import org.duniter.core.service.CryptoService; import org.duniter.elasticsearch.PluginSettings; import org.duniter.elasticsearch.exception.NotFoundException; +import org.duniter.elasticsearch.service.AbstractService; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.Client; diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java similarity index 96% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java rename to duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java index e6674b87..3f501b75 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service; +package org.duniter.elasticsearch.user.service; /* * #%L @@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.JsonNode; import org.duniter.core.exception.TechnicalException; import org.duniter.core.service.CryptoService; import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.service.AbstractService; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.Client; @@ -49,8 +50,8 @@ public class MessageService extends AbstractService { @Inject - public MessageService(Client client, PluginSettings settings, CryptoService cryptoService) { - super("gchange." + INDEX, client, settings, cryptoService); + public MessageService(Client client, PluginSettings settings, CryptoService cryptoService, UserService userService) { + super("duniter." + INDEX, client, settings, cryptoService); } /** @@ -62,7 +63,6 @@ public class MessageService extends AbstractService { return this; } - public boolean existsIndex() { return super.existsIndex(INDEX); } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java new file mode 100644 index 00000000..a24ab24b --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/ServiceModule.java @@ -0,0 +1,41 @@ +package org.duniter.elasticsearch.user.service; + +/* + * #%L + * duniter4j-elasticsearch-plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.elasticsearch.user.service.event.UserEventService; +import org.elasticsearch.common.inject.AbstractModule; +import org.elasticsearch.common.inject.Module; + +public class ServiceModule extends AbstractModule implements Module { + + @Override protected void configure() { + bind(MessageService.class).asEagerSingleton(); + bind(HistoryService.class).asEagerSingleton(); + bind(UserService.class).asEagerSingleton(); + bind(UserEventService.class).asEagerSingleton(); + bind(SynchroService.class).asEagerSingleton(); + } + + /* protected methods */ + +} \ No newline at end of file diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java new file mode 100644 index 00000000..155e9c56 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java @@ -0,0 +1,75 @@ +package org.duniter.elasticsearch.user.service; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +import org.duniter.core.client.model.local.Peer; +import org.duniter.core.service.CryptoService; +import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.service.ServiceLocator; +import org.duniter.elasticsearch.service.AbstractSynchroService; +import org.duniter.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.client.Client; +import org.elasticsearch.common.inject.Inject; + +/** + * Created by blavenie on 27/10/16. + */ +public class SynchroService extends AbstractSynchroService { + + @Inject + public SynchroService(Client client, PluginSettings settings, CryptoService cryptoService, + ThreadPool threadPool, final ServiceLocator serviceLocator) { + super(client, settings, cryptoService, threadPool, serviceLocator); + } + + public void synchronize() { + logger.info("Synchronizing user data..."); + + Peer peer = getPeerFromAPI("ES API"); + synchronize(peer); + } + + /* -- protected methods -- */ + + + protected void synchronize(Peer peer) { + + long sinceTime = 0; // ToDO: get last sync time from somewhere ? (e.g. a specific index) + + logger.info(String.format("[%s] Synchronizing user data since %s...", peer.toString(), sinceTime)); + + importUserChanges(peer, sinceTime); + importMessageChanges(peer, sinceTime); + + logger.info(String.format("[%s] Synchronizing user data since %s [OK]", peer.toString(), sinceTime)); + } + + protected void importUserChanges(Peer peer, long sinceTime) { + importChanges(peer, UserService.INDEX, UserService.PROFILE_TYPE, sinceTime); + importChanges(peer, UserService.INDEX, UserService.SETTINGS_TYPE, sinceTime); + } + + protected void importMessageChanges(Peer peer, long sinceTime) { + importChanges(peer, MessageService.INDEX, MessageService.RECORD_TYPE, sinceTime); + } +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java similarity index 98% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java rename to duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java index 4163b6e2..9977f5a3 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service; +package org.duniter.elasticsearch.user.service; /* * #%L @@ -25,13 +25,12 @@ package org.duniter.elasticsearch.service; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; -import org.duniter.core.client.service.bma.BlockchainRemoteService; -import org.duniter.core.client.service.bma.WotRemoteService; import org.duniter.core.exception.TechnicalException; import org.duniter.core.service.CryptoService; import org.duniter.core.service.MailService; import org.duniter.elasticsearch.PluginSettings; import org.duniter.elasticsearch.exception.AccessDeniedException; +import org.duniter.elasticsearch.service.AbstractService; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.update.UpdateResponse; @@ -50,6 +49,7 @@ public class UserService extends AbstractService { public static final String INDEX = "user"; public static final String PROFILE_TYPE = "profile"; + public static final String EVENT_TYPE = "profile"; public static final String SETTINGS_TYPE = "settings"; @Inject diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/Event.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEvent.java similarity index 58% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/Event.java rename to duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEvent.java index 6b5d5176..9046f3c1 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/Event.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEvent.java @@ -1,4 +1,26 @@ -package org.duniter.elasticsearch.service.event; +package org.duniter.elasticsearch.user.service.event; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ import org.nuiton.i18n.I18n; @@ -7,7 +29,7 @@ import java.util.Locale; /** * Created by blavenie on 29/11/16. */ -public class Event { +public class UserEvent { private EventType type; @@ -20,11 +42,11 @@ public class Event { private String[] params; - public Event(EventType type, String code) { + public UserEvent(EventType type, String code) { this(type, code, null); } - public Event(EventType type, String code, String[] params) { + public UserEvent(EventType type, String code, String[] params) { this.type = type; this.code = code; this.params = params; diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventCodes.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventCodes.java new file mode 100644 index 00000000..368e025d --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventCodes.java @@ -0,0 +1,34 @@ +package org.duniter.elasticsearch.user.service.event; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +/** + * Created by blavenie on 29/11/16. + */ +public enum UserEventCodes { + + NODE_STARTED, + CREATE_DOC, + UPDATE_DOC, + COMMENT_DOC +} diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventListener.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventListener.java new file mode 100644 index 00000000..7d34b28b --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventListener.java @@ -0,0 +1,6 @@ +package org.duniter.elasticsearch.user.service.event; + +public interface UserEventListener { + String getId(); + void onEvent(UserEvent event); +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventService.java similarity index 78% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventService.java rename to duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventService.java index 745063d5..0ce4ede8 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/event/EventService.java +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/event/UserEventService.java @@ -1,4 +1,4 @@ -package org.duniter.elasticsearch.service.event; +package org.duniter.elasticsearch.user.service.event; /* * #%L @@ -34,42 +34,63 @@ import org.duniter.core.util.crypto.CryptoUtils; import org.duniter.core.util.crypto.KeyPair; import org.duniter.elasticsearch.PluginSettings; import org.duniter.elasticsearch.service.AbstractService; +import org.duniter.elasticsearch.service.changes.ChangeEvent; +import org.duniter.elasticsearch.service.changes.ChangeListener; +import org.duniter.elasticsearch.service.changes.ChangeService; +import org.duniter.elasticsearch.service.changes.ChangeUtils; +import org.duniter.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; import org.nuiton.i18n.I18n; import java.io.IOException; +import java.util.HashMap; import java.util.Locale; +import java.util.Map; /** * Created by Benoit on 30/03/2015. */ -public class EventService extends AbstractService { +public class UserEventService extends AbstractService implements ChangeListener { public static final String INDEX = "user"; public static final String EVENT_TYPE = "event"; + private static final Map<String, UserEventListener> LISTENERS = new HashMap<>(); + + public static void registerListener(UserEventListener listener) { + LISTENERS.put(listener.getId(), listener); + } + + public static void unregisterListener(UserEventListener listener) { + LISTENERS.remove(listener.getId()); + } private final MailService mailService; + private final ThreadPool threadPool; public final KeyPair nodeKeyPair; public final String nodePubkey; @Inject - public EventService(Client client, PluginSettings settings, CryptoService cryptoService, MailService mailService) { + public UserEventService(Client client, PluginSettings settings, CryptoService cryptoService, MailService mailService, + ThreadPool threadPool) { super("duniter.event." + INDEX, client, settings, cryptoService); this.mailService = mailService; + this.threadPool = threadPool; this.nodeKeyPair = getNodeKeyPairOrNull(pluginSettings); this.nodePubkey = getNodePubKey(this.nodeKeyPair); + ChangeService.registerListener(this); } /** * Notify cluster admin */ - public void notifyAdmin(Event event) { + public void notifyAdmin(UserEvent event) { Locale locale = I18n.getDefaultLocale(); // TODO get locale from admin // Add new event to index @@ -92,32 +113,54 @@ public class EventService extends AbstractService { } } + /** + * Notify a new document + */ + public void notifyNewDocument(String index, String type, String id, String issuer) { + + String docId = String.format("%s/%s/%s", index, type, id); + logger.info(String.format("Detected new document at: %s", docId)); + + notifyUser(issuer, new UserEvent(UserEvent.EventType.INFO, UserEventCodes.CREATE_DOC.name(), new String[]{docId})); + } + /** * Notify a user */ - public void notifyUser(String recipient, Event event) { + public void notifyUser(String recipient, UserEvent event) { + // Notify user + threadPool.schedule(() -> { + doNotifyUser(recipient, event); + }, TimeValue.timeValueMillis(100)); + } - String email = getEmailByPk(recipient); - Locale locale = I18n.getDefaultLocale(); // TODO get locale + @Override + public void onChanges(String json) { + // TODO get doc issuer + String issuer = nodePubkey; - // Add new event to index - indexEvent(recipient, locale, event); + ChangeEvent event = ChangeUtils.fromJson(objectMapper, json); - // Send email to user - if (StringUtils.isNotBlank(email)) { - String subjectPrefix = pluginSettings.getMailSubjectPrefix(); - sendEmail(email, - I18n.l(locale, "duniter4j.event.subject."+event.getType().name(), subjectPrefix), - event.getLocalizedMessage(locale)); + // Skip event itself (avoid recursive call) + if (event.getIndex().equals(INDEX) && event.getType().equals(EVENT_TYPE)) { + return; + } + + if (event.getOperation() == ChangeEvent.Operation.CREATE) { + notifyNewDocument(event.getIndex(), event.getType(), event.getId(), issuer); } } + @Override + public String getId() { + return "UserEventService"; + } + /** * Delete blockchain index, and all data - * @throws JsonProcessingException */ - public EventService deleteIndex() { + public UserEventService deleteIndex() { deleteIndexIfExists(INDEX); return this; } @@ -129,7 +172,7 @@ public class EventService extends AbstractService { /** * Create index need for blockchain registry, if need */ - public EventService createIndexIfNotExists() { + public UserEventService createIndexIfNotExists() { try { if (!existsIndex(INDEX)) { createIndex(); @@ -146,7 +189,7 @@ public class EventService extends AbstractService { * Create index need for category registry * @throws JsonProcessingException */ - public EventService createIndex() throws JsonProcessingException { + public UserEventService createIndex() throws JsonProcessingException { logger.info(String.format("Creating index [%s/%s]", INDEX, EVENT_TYPE)); CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX); @@ -162,7 +205,7 @@ public class EventService extends AbstractService { return this; } - public String indexEvent(String recipient, Locale locale, Event event) { + public String indexEvent(String recipient, Locale locale, UserEvent event) { // Generate json String eventJson; if (StringUtils.isNotBlank(nodePubkey)) { @@ -265,10 +308,11 @@ public class EventService extends AbstractService { } private String getEmailByPk(String issuerPk) { - return "benoit.lavenier@e-is.pro"; + // TODO get it from user profile ? + return pluginSettings.getMailAdmin(); } - private String getEmailSubject(Locale locale, Event event) { + private String getEmailSubject(Locale locale, UserEvent event) { return I18n.l(locale, "duniter4j.event.subject."+event.getType().name()); } @@ -296,7 +340,7 @@ public class EventService extends AbstractService { } } - private String toJson(String issuer, String recipient, Locale locale, Event event, String signature) { + private String toJson(String issuer, String recipient, Locale locale, UserEvent event, String signature) { try { XContentBuilder eventObject = XContentFactory.jsonBuilder().startObject() .field("type", event.getType().name()) @@ -335,4 +379,28 @@ public class EventService extends AbstractService { if (nodeKeyPair == null) return null; return CryptoUtils.encodeBase58(nodeKeyPair.getPubKey()); } + + /** + * Notify a user + */ + private void doNotifyUser(String recipient, UserEvent event) { + + String email = getEmailByPk(recipient); + Locale locale = I18n.getDefaultLocale(); // TODO get locale + + // Add new event to index + indexEvent(recipient, locale, event); + + // Send email to user + if (StringUtils.isNotBlank(email)) { + String subjectPrefix = pluginSettings.getMailSubjectPrefix(); + sendEmail(email, + I18n.l(locale, "duniter4j.event.subject."+event.getType().name(), subjectPrefix), + event.getLocalizedMessage(locale)); + } + + for (UserEventListener listener: LISTENERS.values()) { + listener.onEvent(event); + } + } } diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java new file mode 100644 index 00000000..7591f429 --- /dev/null +++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java @@ -0,0 +1,98 @@ +package org.duniter.elasticsearch.user.websocket; + +/* + * #%L + * Duniter4j :: ElasticSearch Plugin + * %% + * Copyright (C) 2014 - 2016 EIS + * %% + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this program. If not, see + * <http://www.gnu.org/licenses/gpl-3.0.html>. + * #L% + */ + +/* + Copyright 2015 ForgeRock AS + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +//@ServerEndpoint(value = "/event/user/{pubkey}") +public class WebsocketUserEventEndPoint /*implements UserEventListener*/ { + + /* private static final String PATH_PARAM_PUBKEY = "pubkey"; + private final static Pattern PUBKEY_PATTERN = Pattern.compile(Constants.Regex.PUBKEY); + + private final ESLogger log = Loggers.getLogger("duniter.ws.user.event"); + private Session session; + private String pubkey; + + + @OnOpen + public void onOpen(Session session) { + this.session = session; + this.pubkey = session.getPathParameters() != null ? session.getPathParameters().get(PATH_PARAM_PUBKEY) : null; + + if (StringUtils.isBlank(pubkey) || !PUBKEY_PATTERN.matcher(pubkey).matches()) { + try { + this.session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Invalid pubkey")); + } catch (IOException e) { + // silent + } + return; + } + + log.info("User [%s] connecting with id [%s]", session.getId()); + UserEventService.registerListener(this); + } + + @Override + public void onEvent(UserEvent event) { + session.getAsyncRemote().sendText("{\"type\":\""+event.getType().name()+"\",\"message\":\"" + event.getMessage() + "\"}"); + } + + @Override + public String getId() { + return session == null ? null : session.getId(); + } + + @OnMessage + public void onMessage(String message) { + log.info("Received message: "+message); + } + + @OnClose + public void onClose(CloseReason reason) { + log.info("Closing websocket: "+reason); + UserEventService.unregisterListener(this); + this.session = null; + } + + @OnError + public void onError(Throwable t) { + log.error("Error on websocket "+(session == null ? null : session.getId()), t); + } +*/ + +} diff --git a/duniter4j-es-user/src/main/misc/cities-fr.geoJson.txt b/duniter4j-es-user/src/main/misc/cities-fr.geoJson.txt new file mode 100644 index 00000000..bb4b1bd4 --- /dev/null +++ b/duniter4j-es-user/src/main/misc/cities-fr.geoJson.txt @@ -0,0 +1,8 @@ +Les données Francaise des communes provient de ce fichier : +http://public.opendatasoft.com/explore/dataset/geoflar-communes-2015/export/ + +>> Cliquer sur Export > GeoJSON + + +Ou directement via : + http://public.opendatasoft.com/explore/dataset/geoflar-communes-2015/download/?format=geojson&timezone=Europe/Berlin \ No newline at end of file diff --git a/duniter4j-es-user/src/main/misc/curl_test.sh b/duniter4j-es-user/src/main/misc/curl_test.sh new file mode 100755 index 00000000..4f62377a --- /dev/null +++ b/duniter4j-es-user/src/main/misc/curl_test.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +curl -XPOST "http://data.duniter.fr/market/comment/_search?pretty" -d' +{ + "query": { + "bool":{ + "filter": [ + {"term":{ + "record":"AVbieTIAup9uzWgKipsC" + } + } + ] + } + } +}' + diff --git a/duniter4j-es-user/src/main/misc/index.sh b/duniter4j-es-user/src/main/misc/index.sh new file mode 100755 index 00000000..02b66934 --- /dev/null +++ b/duniter4j-es-user/src/main/misc/index.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +curl -X POST '192.168.0.28:9200/places' -d '{ + "mappings": { + "place": { + "properties": { + "id": {"type": "double"}, + "name": {"type": "string"}, + "type": {"type": "string"}, + "location": {"type": "geo_point"} + } + } + } +}' \ No newline at end of file diff --git a/duniter4j-es-user/src/main/misc/registry-categories-naf2008_liste_n5.ods b/duniter4j-es-user/src/main/misc/registry-categories-naf2008_liste_n5.ods new file mode 100644 index 0000000000000000000000000000000000000000..acc6ea091339d82dacf7533c99e3cf9876599984 GIT binary patch literal 70946 zcmb5U1ymft_a=&KfF!tEAUMGZF2OCq-5q9d*Wj+f-4onxkPzIR8Qk67;SIli`_9`v z|NWn{eNInzO?Q2D>(+N~-CNb)<=_!;U|>*SV3@^CrGst1MzFxZz`UK%zhD4X05fL~ zdov?@ds{0LBWEi+Age3Tl*P`-$;yev&fW}YYG>jCFatWXIN3Xz8JRj+nwdF&|6ed~ zcF>#NBf`MEodi%!RZACuG0@1$)``{mf8Mg#11-Y8D@tLY5urg}!H|*urVPCjL(f+f zc<3+Bs@^5^qVZi`Rf3$HoSB(fP*CvOw{J>HN~)@=y1KgN=H}Mc){c&jK0ZDH0RfSb zkqHS285tP`1qEegW%c#-9UUFr-Q6Q2Bh%B<OG`^zTU&>RhZh$YFE1}|t-QXz*D7L! zfkAMV`6jCBzHqF0=z78X&en=D+`F>}zx_3^5(h14?qI&|G5)!ZM7ArSXEQC{w9}|8 z4AG`L+4!698H(`A7rpnm*3pB7HY1HXOs)R@NMFp!?BD+ln)`<riD`rZjK==-&60t6 zFwHZX3tjf7?BXeo*i{mXQ*<7(L?GA(PS53p1;BQH?spX1&pC4cAnFr~_)(0mHUBow z*)M|rg|}%wRA9e2+qiP#6b3iiSNE@A?*k46ldo16Q{wdMPMS0iXNI=ya8<wlZVTmE zbizMRO4RIbW^<c{#i8=TDXSIcapLf#?0vZ=fs+M6b8WDJ&<_&eiGy(KjBJhj<1&d! z$9UaDe#4mWk}i>n?km^LP|Bh^^>~%0W94JZ3|2>b2$KGG^U`UF7kG~SCfC#A{Z1FZ z(o@WGl_mLYT~depCCBr)L(|-}u2ocL#n7YG3qtj4(#~z$t(jH@(dCNHGz4+wkVdAB zN4xHzzinEZIR3Wvg>dDya)%x<GTlHhEWHWmeRaw0v<@y;EQJl(VID+SQE^LeniF9q z@<wv2eKN<toqo62z+Fko7#tba-m3d)Wtz%3G!tc$fPdD<4siutIbZnKt%RBArAXcE z#~bSwk*FGz>W`<T0T@&_$}!#g$sS48x>OD0#?f_Z<ql6&GtJKG@y6muTh0gKSiRXP zy;xjIkU#7{*WCVP2Zs4vL!Jmg!rhS5Ai>YRC&pEMLg-vE)$m~6pSM(V&8hr)?PzUY zG&8(A&jY&s=_%GEHXUJHW9r>r32Zl4FX!qKqv%|~X@O+%8ab{Qj})8C=pmi>Tb3=4 z9n#Fl=v$4)c6jH7YKl%P%mg*3wo{q84X#^I2iHQvMDqbC8}Gqo;fI3U-|CG5{Ngh_ z9YJujPbab9u`%`RFq%VyLRw=+Ndaqe`WM)53j$WnfFS+AcKYRsiMFyiqjU;_m6+8> z$FgMB(E?Vm`q9tlvY2^U&Qp(FpTC0O;D^jz{O1V)02WeP3gfA}Z8UXm{;C4lI6G(T z$CN<clf<R|aQb-YPAk(jchGZY&q^O{O_R||MS^2iq9<{m;_37cU%Pq(HaUmtAadVv zLHAfCkC*k^C&Kw@@y<;?8c*R1(C}a?`whQ#?9U*iA+ilOxMbp{++n58Y0Is;$V%Ub zc7C{mK3|QJTl8BO-!-?75=6Hy!Q+Gr4ORTbO?ma$g&i@{_R0oey$EH4eh8Q%Yq94H zbxOZDsiJ{j`tnnKc`k+xNzL}~92mnoQ>I4u;`^m}2VJW;`CPN*Up3LodNgFq7gT<8 z{wsbXrBM{$&qwNK{6@k#>p^g?q?;1|^gT@etnqe^tHeE#bzHl6b?O|KZ{3dHRP-G` zoy=BOEk3Pa3D)J;Q)lT_uNKvzUw{(ESWq6lK8^#75$uAUGUl}VsGottijiBlK{Hvh zzPm~RJ3qlv6KzEuiZcSNS)Kr;$1S&t9$gDKB2-W~FDm5og!PAN8(eipW4DuS$g9>7 zs|~y*pr5g059*0vWF!>7Rf-t~{0}dRf`alN&;0h}e;)6kH%?~G&Q?GRCl+^r?Xk9w zQ@-qbU*S%w?ZZFi{*!v;Jt~%8cFf;V=<7Ek5xB&B5g&ag`h0D1mM}y#PY_%&Lx1@m zx40AN*(%&h%<`Is_%e@P`0j(ZT3n}0Fh`i)2Bkm;gLB2see6HshEA42%OG9N>o8)( z_m@scU-9K2Y47{XTlsv{!sseL{(WC&{9Z(T8<-UI-ma(APD3MQNL|WY=-+hNzo5%% zZKghlCuSBOYG>VFYHG|Z<G|!rgxCpp1GTbQJGBpD`nWW3j3&uLI5MqtmA?36bCMkn z(aOI8Pt`3?3)|XV2b&}g1fM#->X0kSgBVbfX7Ld=HL7r32cH>Cs6$A|t#RNYgMvq8 zgs^&Lf;{qQ_3%~$z0vMqW88%X1h>uyS@vYNH$pu*z0vc}O-uHR5>)^Ex>2(y^FJdo z!WzqMs(9B2YO<oIhKm}zWh3lsT1n1|QuzQMto`xBMG2FFNYhchzx|+8$O}Wjw&1_2 z4?iR=L%;M$T;000Xn(aO_LCqxTI%VJ(+SE)>>{}{-=lt!7HB?xK+Tit;VC3(@yN5& z>U8*?e1AXpLUP8HQh?w5jR%ITq<Qym^*khl%>rN;%_;WvBahC>O70x$?x%tG?$XkR zsK4Z?`CuD<k^>4@Fp`SIePH^&tk16IYhWVqeE!stXPHq;D&~!1pVw!F;6{DQd-upM z!63*uKXJLzlsrD|bl)@n*;nZNlJd#h@F4T<{_xD{E3t3KKy(h{YG)40!?%f?PkYox zudE2qdO}GP28sF8imWA6kF;P%{RH;7u6}R>3r)u<Ogi6dg5NJCn!l6hNp!zeP>M;> z64|u1%cK3rl>-3eT;kG$Q@%I%hAYUO3hw$7p=N7R1E|)4cR@hW$4`UgT7Q1w6zL8c z8aSQFqJJ^+#4!x)VMY;<zWiaVNU;@Q?kLh}ru5<VL&%PReb~5svrqr%xA1UL%Mb8h zFq?SzB1g1gS8J~Ygn2NKOpQCIcd4iHt{O2q>rjO8R-#C-+9{IH-FHdvfSXttS-5T7 z;v<<RZepmC=y$uaKd%cim(dBf9)&A)NW<u)x$ii}kI7KZ-ifjzJYEU_<<<}v%A^vM zn%A+DrCye553f~Jo!~4VUPy~J$dt?35bQtyV~PD6qtAdYp-u2`$r5-)WFIG}!yOdp z&uH8s8W1OzY%9N`rY$v7A~PpO2H+9LG^}>~Aw@9Zktf+Ow<<B{dm?+FVo+<M@uQ$) zDcR7D7F26}U5Q*L<pTkC46N2M+3E$20_&D*yc_l#7g%GewDpHA^g9I)hD^36OIIN^ zbJO)w05)ITSuTJvZBF)`1rh^0Fn#Lpj5&sGPN2RP(T$y2cco6UDjDJP!@nOx1;9Z% zer7L9Z+6Sqvi^|M75*ZSJ>4AEgXA6^zV)qSmRAJ`bx(K|3x^9y&}*{hYKz|~C?<~v zIdH|6%^UMR*Do}<az7V;2spTgs@OjTF`<qjuWU$?-v{y?fobfSM;Eo}ZcKN)evbCE zdfoP+gzz-Nc>-S5`W>IYkkIK}|3J?HwK_1{;)6LRgQFAsRPUrAme(`;)%Y5%Dph%0 zhlzQ{I=SQzN-BD?y7~M6N?hB4JZj60hp{1ogj#xPJlU+A1y;g3t%7^QH;ME~{Ef~I zE~=J|y|KS4WlvL;j&nQ*P+qJqTF7g1%sZ*%Mpul8KzYc#w#t(jT@+Wnw?}o_k-Szg ztI-!f!SKkQ3X^Qbb72OpZ4ctO#)`NT<n;MmtN6?E3B+zaRxn3m!d^6AQ(EqC*s)NQ zH2!1?p$MG`#vKh!V3pO3c+Fk$B%}xN4O~+AGJlQn6W)hib3j<ZN(avf<|E@te3HE? z$>olQFFEy(uy)|r0{`xf-9EIBszw3*DqDsZ?O(Cmjy=}C85nqyBJdb@zetncUI6bb zo$U&s37AKuElA<7wxj6Cdr36kkbjp$M)`Xr-qQ^Yl1-UlVE)g}|36{5nX}QG<BwJu zjhX)PzT*-{9YP(GNDEMA9JSV1z-3Gd$fW?P9gN@w+GsRRCrfR7b!Q{_OwYUs-{ijY znBkziMAig%EJ|Ft#T3>}Cqj`{+OjkfFg`4miQKeZCMGECgjBCyJ|9r;j@~F1!+>$D zU6(tVP&#BUZq|S(&Rk%P?3g>@#Ca{Lw@ugl#D?CX1u6(4!3;f%3zD^y7RXWzKgFC# z&$0(eqQ{mJ!opSrQaTVM!Y<N2t$t^|5lB!zFrE1anpgqiNYL+(!Fv`QYU>jJHNxKb zsNVR`3o{f9d(bbeb4N!Pdfdo1ly5%c$e63UBuMrA2p0NQz%yXAjZ%QPDn6TA^O)&m z0y}FM_anMXUbWAkejt36A<g`mG?f{DDEg41anfHO#cK+N`H8!ED_H}M10zYT)jI(x zmqNpfv^!_%J7g5yj4`s{ozyvd_QWTsCD*Y!00u;NR{-fcoSlys)$X2mVczA5;g2Ut zo2W4#Ns5LoWu;8`(t{il%r?C<`?IG|y*PgBskU^v6tr928xbn4(%yrPRLFM`HxdSX z*<p+2Uqx@XF+s4X3!#~p4I#HL+%iPP>!0L-JG$KcvtaMq^uoP&)eBa$ESZIq+^sEk z&A%O{7N69PNEA=UH1Vf2$Kt9Mt(jpsW~KSj6LJ~Z-y8of(h@ir9x^zc2!m6`+uG=- z29tT*Bfqg|TV2BLJsb=S6~ceVqPHx|$=So!?0*Chx`)6`ZoHRHB_sE03H0*(NT<3C zUd?)fBe<+C!-eHeMCu>xO~2^At7cy93XKb?<rId_JX_Bvlk(5Yf8n6Q!oupjJU{!6 zzal8`=zJ06@lDfSn2R-P9|4)$NqTKr>GgV8>e%(6`e~TdNzZsQxcdmw|8s)sT+D&< zXP;=jG{b2L8y4O>iBH-H1O1a?5Oll^3Y&U5IaXB-nQ&$9`j(F@O?Y=&?*L3@jDX3_ zv+N}MmWJ+0PfHVe^wi`GkgetBZkRR8wCzr)bX+>__%DRcWhD)<Aap&0Dlq=xJmO|H zaBSB}Z*~W1u|=1_skD$prypl{jB2^<%eDMmKO9Db(onJjn(^z0%dQEEpG5i?Ut>fN zLKr>1%KvCl2sTh!F?1F5ApNq)*n@m2kE-#9-6=%HZ0FbFgLFCHlUJEvfb2E6BnM63 z!Ozk_xe+dDREF0*HQc#H(1eLcja!-)#13YUk`ruUk*V_P1!tm+$Zfb*HAnx>q8@wV zo)8(E5f$rJO1MblObI-h@h);AVP|m^Us~_NoT$0@)A~{9IJ>l#8~eI>vpyo=D+g&Y zvpmsp_}X=-EYo4XUnOIn<I@4akf`<7U<u4$9R!_|?ctxp8-Hbr_27_vG1)%XqWze) z{W1C7{GA#9Z+UQBLdcM6bbgv;_en}sYx(pRJIqg(S!M0#@_lxl16`!y%t+hvuaM%& z3l`KL9_OE0lKY^8IED%b7QGrk@D?+ub!_iE$70&~R@*k&_M4t3#U(uy-XFd=aGYc$ zFhW{72;)OH%{XL!rloe`h_k(F*HR(>cg5ys1OUxA_ti7@Pc^Z7_X`fRd_3w7QOd}U zszI9IP_ARCC-SS)=GF3qk@t!ER6n2FZ8xHLOF@HD-}b3%{SF%|==sy5J;Co}h7*^* z5Jv{;;Y@RyjBu7TmRc6hr%qYXl2OG7#QaOba}1NxeJ{<C$PkCb%rryt5fx`3YPz1K z(z_EzQSr!J+{_YrTemTn?#y^gv|?E8Cq|8wef8-k*h()LiIN=AC4eqXUWxYNk)ccm z*Bn>a8(5kogWNm*9O0CQ`=@NI?B!nT{x{;&wq8O?(Y{tMH7Z|Dw3UKog8u!d32BOI z<YSVJ4<BIfN(=C^S}^%{NL;a50eWeDqxjUsz1Rh7IB5~+WScD1{^mVWs_67_nD6{u z8o+JkbDK<<vmugJV5jQ@mSGtoB$xU6=UzWk&S^CoIa(d6k&D>Z7gn&QO0Df52vW5b z{y44`ec-Rhz(b-l74pJ4p#h#yJGj``f8L%5m@+r-mBRY&7Ee3Qdv0&Po_{Bp!U0r> z$@>&kwOYhmjocPBvSD7Cm4CeQ+O;{2E2jQ(VAxI6dwN;A+7vv%xsnN1*c_nTEkw;H zX~PmN$JRQR{1nC|smTkemOO85C;?m8jUU!sj(o%X7B`=burC*sm_e*XrPeBIqbv4@ z){cit$54M@W+;}(XWfD+f7zQYRHUF?&a&L3hPKOHXH(76lBd1Z^6w7r*}I;Q=<e#4 z1!1-68DD>fR;{G3^0Og(l+T(GWQf+{nuck1Os*nfWHuo%+;)uaQIVrH1^tb4ZXsC1 z-W6wg=8L@R;_*&FXL%$oUU$uWNoQ=zAv`M&XcL*zSG*FIF(d+gok<grl&IuoO^qtN z?8-iLB@OD}lCt)HDyu#-PVbaVD%UP~@N)WkcQBBCpMU&)UncSU-u;8MkaAGEUDWr0 zyJp-0t$h#ryDxaWL%O91rzO7-^_Sx#U2DH-rmqx<*g@F;wi%zm-og{3me+`wD5Zi_ zJ3O|jh>}jkDm6z6jQX-SEGss73vBo@h~J%tYt(5Od75IUM)c4$&XEg^temlx&1~$( zAzrjE$7e5(vnXw4-?RvTa`duyNiUteT&u!b?&sU7#f>s@-c2`WW0EW7otlZ1@7ot* zm7Q8Emxg};xJiWu;77Szjr6V|Mc?k!_klL)fNli6AN)^sE8g8p_TJEvn}(mHZV!OP z{Q7$#aaEX^181&7B7I#Yp*6)G)gC2SscwrrDW<EWOxyFzS+l3d*{G027eb%dou*rb zS5JY3%kG+$-2|-jCi1O3R}cvPi{zZ)(epcUPUn)IQ-8UX#djB}9%tdxCysUtdgRfr z1%jajv96%4gQ|`aKAe=w+a2ZGNNMPsI>&)y_KbdX3(H3eppz1T&#jIzciMtcFC^2| z@a;cy?2TBtwU+JHJK?#tbdFoczkJ}wIWHGnJx0b@w!6*QJNU|QUhX8{i`iE!*hvV3 z(GTKsh_GEaSm`08H9@A<KlO<!G{e3iPaF@rtV#e0kF!Kre-9my8GFah0+bhi4auzA z;>E-GvC;J%4)0foQx%BmxZ_TjUoZ&f%8oZ9_rl8c(Cs4F_F>kGq5bjpA}yq{YNzhu zY4JX!>t1RxE0lTWQv@rB$Nh>i=7BVQE+}lb|ErZ6`_%}enUN<}GKzgg*1pO=hc!b* z8Gnwnupb-FVdde}u}z{ihNK_&nbfmNU-5%9O*Sfep0DD6Sm@Z7XHEQpD^oQ3Guo9A zusT}#`ZGS0c9XZ*g(aCn)k!f@b7Wf!mBnMsf(Daq$l!5s1%7Z8C>RJ!!TM`O31AuK zajQC}CFUF2F?#r29+X;_!=dtRG>c+%i49?j>K9@*ryr0rdd5b|urMWINr<ztp`5C2 zpqog7l?gA2%?HiLX@P3yBO_{Tu4criJTd`z%>;_E?q~sV)V|+?fyO5#zic*Y>=)5s zepg{xr419fH}9yl>{TcWFaNINQKjD?Q~iD<R`Y{-E8M=~Djt;*%W`P^bOLlNgYmUB zIv`j9zN~K#10Tb_f@`F~VvR@Ze4v@y%0f?fOq`(X-E|^X$V1v3JNzZL4Z>mBNF3bf z$P;o~@t46odKr<I?R6vxqHX~f%q&_>>N5}SsPXqXoE}Rg9~qPsb?l|{xcX!q=5hoq zqp7LVzv%0a?2<a$s?#X4?^fu`Mj(ey3`kZpp&(&kY*tx((2SnOEbD`aUOQr=Duu|u zmW-ZT7P!$GS_Q}E>q=xN7X>S;ta7)=*C)3z!<u<~=Gc@-eIc&+qF9&$jdR>Q+rKcQ z+OYhFWi}dirTB!_6(Wh5W0-jRA`wH=PB+lPS@$AqhJnqlhCAV<FhR@<47joJ>@dfw zRfnIq<=7l7*Cnc_fh5{te-%Ywj{F2(<?{c1rAn{-tKyo)K=NRT_TLT%{?yDfX&8Tq zM+G6rsK2H;LF)ro)C?MZ^V8xd4#;Y`c6&^6DK{&AF={7%6L@ZRgcFD{-z)aK69_CY zaPh9{8w}USQ+0byzVy)gLhprpGUV{YROQ$1==U6EB6fL2t5!9OSf*UzoHM)E0lNrj z|02D2Cao11SL+$y>WwcwibTof6)3%bY}2sfOZ$AG^cGk)(XB;OLgU71{r@hod|R6W z7y+%!&77QB98Jwf6Gwr8U)~3u3Iv;tK}1*)5gqA<TC8*aa)oFjvR<TrT<M5ORR2*} zdXO`-St-e}8dINfY(z2;S^K+7mD7ZtW=PtyVV{u?#L`l<$hQD#WF$`~J+}@l%3)}A z12TLyNDgM^d1pIbmrv)Oekq{TM%6XT>mdEEuRaC^l`LA!>UztY=<C=9s=3Aw=fm7M zk6Q}C$Ys7ixwU9UjmqtzjTG@eMWRvVhk~2~Nm>=<dO4CWe|+uAJKpFwY@ZIy7ce|y zj2VZlguN@?)?aI{KG6+a^-i+%JTdWfneIJbrT%4@$$p}A0OI#}-F;yhe<j?gs(1Zn zD>7DK@esFLe?hJ$RrRL6`diB>L|7P@K<IYH|GGi)KbQ61u0+sN+zx1NW#QuZpY0YW zc2;{kdl!4?9*Ya>|6M&NXCr49Cu1YW|EAvChRpxop0k~u?f-qPHv#|eYnj{H89AH% zPYM64#m>+Fg@i^XCT6x~PzBjJvYNO!I=<~cLDOM)sA7@d@?#ii{_~&X|Ew@_==1-* z<5mD83o|EHF)L?)k-gJ@qqYBPl-{&vZv-^6{cq}#{1=VdJK9+|nmPUd#_7M(0xq<s zf`qClvy7r7>;Gq8&^}kF$^pA6-Y@Us;4KbKn5*USYN;NePQiZ{aE&t*8F^3B#2WKU zNv)`K_GssE#RN$cfZ4_4HLykHx*;JHi8Rr-vJMH4J@$HJ;6ChSAaI6%e^@s;BUBi< z7@qJ$N<4-$9j0*pAQk4(QVz!Sxm`aem1QN`zetlcopE(dpuRzvylPhbyh9<xE$jQO zNaZ?AI|nm4w)4Njtt>9x;TbY7kB>}tF0!(O@=E+Lc-Aq$jw^$dIL8t<>gcg!+zOiw zCBLiq;-SMjEn^Jwjq?w@LsnPu#H)@Tn7${ECiWlg77dRjBNGmElHc*Y>&7TmQ>y#A zLWK!rx<?R|Gb=~LnvK?)Pq;kgzas~e8zAClej3;)Y?nwK&^YMVsD6QaV}#heX$%F_ z^d$b<lo%bVViP-{^Z!exqxNOs(LmCvhp>|93@7H&-(*>t9gBGomaAIJgL|JVv1?YJ zAyG7v#E*{v+O%G*Sr+mu_{>8O*lr%Z{m71VkW*UfEs^AxUqex(d7s1?jU^D__gL(< z`#%Q8KfJ0Zbdmd=8wdk0*XSz_%9&RDxIrEFOwy5sZNm|vw>i(ZcAYKSem4fM>wb?C z9)3AA`}l5VQ^4u`7TKZv{TH6S6}=gQtMQK4@`?!7q2{pz)(H2EIRnl?{B0KI?A7bu z{j$#7&N9D3pzi>3XI97E)w04v4dH|N#X{X}UsJaqhtK&w=(P0iG0%>+>e%&QfAMrU z&+bKYSL4>K3ETu~9&E0-OSS0DS@FApaDrHcUY{OMJ3C*5T3O0M$1O5Ocbp!M90C(J z4_yIC(;CY(IVB#lzFN=sN1iwLmo`mS*jUR#7Yox03=78qZHyHz2W&s~G7v)rD8;#( zhJ3^*SaH?q=HAnj;A!8}Gwx_-i>(sLZ)`7eT$o{U`7Bn}+3jHkup}V6<Mw=S*ZO(i z&-d~$Wn8U+^Z_(rGw!OUYzFG;J~Hrmc&TNI&5&K0c&(Wp#jpeJ<lGA9v^_j(7`*r- zaap-sTx`vze0Intxpi;BJ+41-sT&7r=oTq$QTshnQ}O2~w^2T|`90iiJSb{EygrOp zy~YU?FWznU&Tr!zm`N=6^bTkL8TceE)q2+^>t;4f;x_Vpc6MyeC;(hEsT<v%6wUVW z-4}Op`|NGi^mTGqFUr}kM?*>L;rZ1!xuVLCj5}u<KWk^d%r14=TT$QXDe<WIk>;r^ zlse~RxUu=rBzKi?zh6(=Z0iqiiQVHy`M>?oysWsg?8RBCyyy>1qdBh+vEm<->4(bz z%RU6HSUxR}8(Yh}OTQb*LzAe<uP#oqs|KDYKf91}<+nWF+~y?iR-$n8f2m$|o?;2g zdG2<yo3IdlzCZu@SSi)q8k=}^z@l*RK<+zf@pQhP`<iBZ`#3J7s<q@Z*LvsU+r>TX z=kEJ-30{3tn^z|c4_8`wxft1B9CXsavYI^p!=lu<t*4D^!8&iIFeP!~bHmmSx*Fcc z8!OCsS+J|@96!*$ip<$KY2U)y{CE1|GG~<ROBX2IBf$&_d;NGi^su{h_<(#3TmbKr zZ~6fZR?8E?@8%214``P=tTIa3K?R*GceG=di@f)V-ity$%n)v0mPd4>*nWVD>%@2h zqxSAyiTJA*cYa|esUP8@clvKVti-0pjk;a8Y$5lC!vu<obf${H$BW2ChUiykmkigu z3EWSe4=q$)_qX$;`@-gv*Vc{8Q^&&dnczld&IW)HfxgtR12I0~l0(L5Ga!R_(Se5t zd}+DV$1D$6vUU)}>IW=NK?+xNV>73dY7}%N!IJ~gI>d|3qt^qmi+zyd(JG5bhed7> zTcg9e3om(#L*K;vvC0(cX*csIjJo-2TS3Ros2a-5s!A~RA3hGL(3sLPHe!vzh4|8^ z?8WMn`00v+94aiK;`wfqTY#yr!V7`H>QbX()sS8yVDstVs{PhMzr=)F=fX-Aa@2cm zYX$_-kgEnC>l@hZ>@@0Jd{BQ#4T}GB3^YFPf4uJkEV;bS&a(JQS3S5rRX*;L4>-Lx zHorF6O>rM~&}U<vnc6>}Z-dHLw%O?Q8v2K}cs11pbmLD;Plboe%642w%p-+YOt|@b zAhzcvUL)pG58NQ(K80~QU7yzT&aCAnZ`T(p*Rdd+hpqOiEgyz%_jK}DmZOb`%%nVF zT|vLsj@ZSvb%%-e@W3vXj>GYrC6dn78n?KL$1S_qCwiY8T(@O|56=$^9}3FH+{k@Q z=D9u83&;ITN|fUJtJ3>RZOO?V0lu$VKk%yBe%~AQw}-r(T*7}@*4!&_x!L{2xd+<W zs@Yvt1`M*Loviz=3ir8fp<I3LY-c{1iU9tbzY$D%%--&IYEJ}Ebv`px)-}7YgiL?l zN2swtv^zQMxPW6f_VlGv63!;^-0*!qM@+!r6=GdQc%kWjOjMnlzaKA)&e3=XHzwyj zKWBTL_r13r?jU&gXj`W-p+ZwK&Y{_M({-BTHQ)yk)K5~o@lL)x2p7od@wnZ!zxf$< zd6d^&I^0P0c_(M1O<}*};$A|nRc@0yF(&8X1pd+o_tdgZb;_!hN|?M!plqtpkJheq z{F`4)KVCGzP*)&sd~>u!DUMm-l@1)Mrv9{FN?&zS7xF0QO}2OW7}GB;jQ()R+>ifM zQ;~7>f;<OHwwafaa+gOuytxl7vJiR@bbaAwuLtM~n)a$?uecFAU9vY`-uPdU3k3;q zuHrG(o?Z!QdRIRc25h_cxu6Xii`oy2_1&$ddfUHyMj2F0hxIYy&*<<yc>*`*sE_5j zd^g>T)%#Z{u~cZ=D&<{kwyXR6d69bQM5*#9sEssdsVQ2)RnlM;_FtY3tzZsTjg_5E z>PcN}hl=M`@q?Lg`)fJSxR%1R)}YN>*qCCr-!xp<Zg=rrnNS>`(uha1Rj`isk^%uh zh~D{A@9tC?1_Zlldl`#0<@TtQv!jfBy8b@Y;L65gpjQR3@;W(VB`#z>K5U)P{P=#> z>W3S7M@dSo^y}6zYul-ixrT>##tzpl(FJAsRA{BQOOo#=eAfiIn1e(bo#(?nzj?0* zEV&x@N{4h~)BU-@B$?Udr*O5!X#jm<qtGDn#TGE-)!rp#?o-)=FvTR)vY8M&lk~H@ z@QAM27La%6ZKmeKJSpwl_geHjKZUo~cO01TwtKx2UfE?5zeb~9KH55%Jh@dSmqm77 zrfn~sRt%jYcK%}8*>0>Vb8AC6bBD?CzCJj+n}ChbQ=oURWc-#r|J-dS?2WA&`aJFO zz61SVMuv+$?o;D%SThFRvany)tOC)}2ZAJkPtulyUdc79kj3ds@9xtLO5@qTnDphK z4+`Qy{5GQ;-Ir*mwbi>mt-Dh6ItTvi=*~FZpQ*c{?UM!zK9vCzP!S2AgJ1i}U-p|s zf1dN>|MmM-*P!P|#Zz}UfAq@TYSMX7+fpI@gg9ulT4cJ^VA3^?>4$ppvM_yRk}$<8 z(xpeNI-m3ULh1pywYZ)Df)pEV(hn#A118J(5hp+S=s=%0k#91GqT(fgAO2hMQ(y5r ze*t$s?JVXz4_}AgcfJTbJ&x-WpE!Y~{o3-Y_BxVBLQXuBm<2-*Yr=Vn{|di8<h<;^ zKAm<R_MNHmg!s|_JM4TsbGu}%d_5`aTpW41-klt`!wI=Ig8UZRBbuq|*zdc%ell2f zdyR%Zd))>#+Cu#NUS@@#Z_2P%UoM)KUti1p_WUx1R<Bq0`=^mRP{y)fn~qMN=BINY zRk@vyyE#JVr?0;15mD>L6)E@k>0{$Z9=;t<pg8Zn*82}n#cmcw4<-b=ou!tSPpgmQ zFZxCyul=9B4x4jgy!Jc<$eSlSqaN=fLK);9gs<I1pT%FUp0Mv1mq}le1&Vh!`A9zy z4_C)#`Ycu)+Wj5aeS(MVJ{eSY;>@)^$&g(l4+x!k3ZDvoX7zP<t~+i}TDeCic~RQ^ z+=Nc?f_8se*`3~&<fEV;HnTNJk7P55hgolm-mhJY2pHU<W1bfimus8=CkQH`=3olP z_rciOxkdl1PPR6oSovZSEy^1Z!HHGFR_PG<*}m<Q7{C4$kBpT9e=#6EMdEdwLD8~c zrb$6z@p$w^4t*^zt_XffkFRf;X8Qy#7$k?Wr001e+l=1W#D9Wj^T$!6&8V-LIQoH` zk6WOI5-_|+D{}RfY;yC2R3aWJv=hN4LzQ}5qa)xb34TETYse9<#LY*<c}9!@eGgN) zI1DI^DZ0%cDjg_HoFF+emk?=9nyu~SXP9Ngg%A6cmYM!PU5{Zb!7)Ew6+}T|u*cf) zmSD2_j}rmM$C&m1v`cew?NZq$SR_d|io(M%G%7L#U~^d}J9<wpnk}c&ufE>S0}$QU zJT_r;d1AU;#MfITe;!K0>++CQer$fjsDxtJzhN}9NcmtqeIl(9wfRr`(OP@HmKhOu zbr1W00`r*y^t1t+R5XbQearLF@M|=I_7QE%{wsdzz9AEGH?+K7kMtL3xPS41er{K| z_$s|srqfa9Zcc=T_yHe@yJ)4>go4G}<RBNk?n7i~&6kaJY3N&7Es`sQG_2&_#S{F~ zKUdPEs@}C5K4#=<Xv!0|ZzymCuh=i)ij41vMd=*t2*UOl>xukAx>d{Ia+y%wW|7;t zq1GB!Z))n#S)U+o^<SCbx(X5WwBX&taLMJl+DppAxAnumLW`s?&}g&jo8aFm%^Lm_ z9&K7aDGxKNiBES8cI(H&)e6Iu+8#h;kgELg_23W5#`mvNqT2&1T9Rhf&kDbpn2P1i z&Wi~PE>ee`$KXB^|3%{VhAZy{IfpfTgN`q#+cyRGim2+OB>#yLxW0E7fC$)0M*_@w zP23%ENEndP)~8A0E+}Pn#7VL^u$et<@U{GNz~5>kN`nYU5)L7tM7v~tI81w}yR&W0 zCNCf0Yt6M)BC|Z)aZkG$-lE-hV_s`9!++A^03&GEt>pGf{h?1iw6`2>`zD9xBpsRD zoXS4pAgH~bitfKQg`4r%80!hg47ZY`VymwIuEgJ>PA|@NRJYD`)DwVD-cW2<CBDRU zxx+$#L^-RsVLVjbpX3(2$s+c^`MIVB0DSxmvzCOB!m^sIrI&)ZmS%%bqRRpPq+7T0 zuXi_x9`5kWezYx2PS#KwI|bVEPX|3+y}cO9R0_J954QF&*oJ2dNJxoJgs3&CeYou= z`)<tlMqZFrGa2;>o$$EDGl9NYZpEoaKUk7#J$sK)-9(R3&kTI>lw$vi?p>}+F;@C& z%4mJ4Tz~sqJY7&L2Kb9qdy7F81z94D{!CnxRGbc7pSYbAXt)pcjJ>PV#Xy)Dkdecl zLd(#r@NnozlY;5+816)lYSIW6J#Qz8ms2H9Pp9?NRvKjdgvU&X5kr<I5Mwp>(xsS= z1dYA-2>$_pi+{rNj8A<BvVR11S2m|IxYA^pqk;4m^bt}%QO3vHm<})ZA1_t&9-Rqa zUeWvJN-<?`Y}MEVUGUn}cE`p|9ZetxLlMQ8j&O~=x6%SCF`-3VW92<DhMu0@UwM&b z?=!IC9NN<p2llv{pRr-8jVd6+J(<i!oy<898t3Jp<;L6FI36WTy@`n6N~_*~`iy;{ zdGsWQ(4sygZ7*A)y1cGDVSlrB%{|NqaM(;w2*;cyt-p1*cFi<d>0xem>h-br6z1dU z!qG`Ja$y5{Yt01LvL@uP@2;Fv!iE%DJnSp151EOE>S&rsgNK{k@R$fc6Y^J*WC9@- zpsD04q_PYNQc3Dr>=r`qYedhko#0Z^f_M$xO>hPSNTEfqp+ZQ=)h{T32{BlOo1Eg7 zC@TxH)kivwum*tW_L1X%6(mdO0iE>vH@kLy!)rCCLifTZBu_53uju}U=ZT$$1CFC* zhuVPdX8OPme5Nk^)JNipVktJ*PsH58SvN|cT3L|~M*-wa&ZAg(H@;tlU=%1dL&=%0 ze812KP(B+%X@Syr9XN`7*MP7e7OlJd<`alqe`YbyGG6iyi-LVya$(T*RQ!Y6;i6)B zRpMszV9?v)<k7UG5mTXTHFdx>yo5z!HWD&6QGaz+f~gN-CQmSPbBG5}+HH`uuAQOj zIk^Y7cSnx%tMtsSpP{vP%gO3WhrUtW!GTo&61Fr79QcvA9SM0&C=fryg?#)Ur(;#M zU`Anc<?ttXy~ynV8%F2R2HcxU=KmqR4dYcMGgNv#o&QMh0$@h^&7nUkV+u#S<a-O- z#>oV8AICYbGUr?;5+gTbwrFbgAx_O<+??|ZniR|LCMxiZ&X1~<4UUQT!PKM&6$52W zrZ^#stE9oby{hy~^PDO;`k2+0x|njNI2mL;2=2BJWUB0}Zi4^;OvNLss|g8AH|m`M zL~)I+^gPA`3_Wl5bmKZqXc0qCnOmHw6-v<gD4RWP%s9&^bVXZ5YD`PG5pE`=`^Jbm z+GSU-wv1FDH@hct9N%xuM_{bzLK!vwh+e$SPUUYNw{uD>v}rzQk;hGj4a4f>Bfd%> zXJw1cSCK3SHpZGkTi18k{xwFM{exR^U;hjWV@o4|ayKJ%ryV5PV<NFcR8Z#fHY3_% z<RT2EN#C>_;NYS=42$_rM$9-6+>;E7T};|X)QyFPc&7n+t`OnEt$N=DXKWS0cz%5( zhaZLHl<S89s*H{brMTD1%t8<HT^gaPj2Ftx^xl+3+EA6B^(I+YgfImWC8P|{NfLw} z8e&#igmgWHl3xH>^plqbt33R^mQ9#RX^XEO&?{eC28-VoqKbibCL=s!@p58|Q7XC% za6Mv^e@(=ZF+<5KX5v|IiDK|+v3c#A1lNiGl7*{f>U`%@^YCza_<s0vqRh{Nx>Dm| z20l<RB*L>7FDJTquUxza*P}Q2*H~P{8wQ^VL!;%Aeoz_{Rs2_5^XcW!K-1LbPK%PG zKaWvNrNika{J+#)zB{WVSzt*l_`Y*B(*Zk7PH?$D2XBS?@MMXSHf;rP^U9X-wU+1C z+r(7<c5^-_w2GMGBhia@bC#jU*x!Q=MKNb_L8RoB#EfVk1N;0jY31Yc-=!<I%iT-- zrGQ7@bmimKF*OAvb%#|h70sE9e|Ixny*{MH)dLy-3YS7TgQ2GGL_Uop*?mWaG7E>3 zcE<f~#3fk@%+YO}xcZ++PLxM!u|7b&zEp;ZsI@?zVu33W3e<Dw&lzk<N?S9K(Nh=} z!ujTysfngPNpjp!4URc}NNUq=a)T4$vZeJ%3*!U^f@&4kI_}5RW(~QGTu#!7Y*)&< z?!I#b&9-22L6*w8Of`d{gja_WzJ_6*(?pZh@bG;hHMg#dUBhD-hQcs!r_JW3m>`6; z(n+l=MJ>oh;f(7P(ZIVSjj9~83%rEhQ5Y1A;|-Mp8&Bm-BJhT)ObA2D^zXsS9_ra_ zz3!zqH>Y>3+O?CGgckTo_i|;t*CLaC=-|w0v^_^oR!ds=jhbd`&$^tzez@Ue);e?U zWtR<>D1+dq;aCOS+x#gIr#q95BUp}iskF5jPuwjPb?IMAa|a1`IV;y@qnpFIkp)^4 zpRTS`Gz&|`pV#f)nI|;|aKeKXY2i`q_5O94$o@`uNn4}B?6noo%$P{)7}PEo3FX1_ z#uE+WVlTQtlx*QhB2Pl<#baM;lb5NZX;jL{`#PlR_#fBlTC)RxJw}<VOg*az$(@Rc z2ubzpT5i*2MA7%Tu<=!pn2W)-Y-bzhqC!-GbAMeym<u!)9|D(+R#nZik($&N|E{a6 zDL0a|F3PXrDJq+z0=;Ai$z6->%fywrZxh(*fs{06P+My5{q>6(w}U*p=o5o82C)5c zulG6T@%`&pI%7v}no#(vQoj*~vw!#fOt5yR<eMhrIPNZY{W~kdw1}*Qes42aTk1<q z(ZAc?#Heq5m3aU+(pJW;Q_y0$bQx~}IBiv&<yBoRq9Wvk7ke#OB|-~sMW4Q}Kb!vS z;CsYqyRtl*zI4%>BX?&@GG+`ujnY-j{|DZ?0bv<V<LeF57Hfd&)pSjdlijC578(7u z2fhK_wWa673{2gGolIShc267PlQ_vP1f4r8cQn21h39fmnTAvQs%CT1GjXP{3Fr0- z(qaDbxTBnwge`Aw)sHTG9JPLoAJPORm&q>1+LLI^259v<@G_^;keoIcdPk&hG-EPD zmTbBTNkV4=-VBCB1zmE}9p}978y)vZ1ws1ZHv{4(H%CSUc6Mr)@9K!{k8YWXaw3+u zNxvj+SCTufNPTk4$Sl-|Ovg1Mx311Z$z8X6i?(o=GpC931pSCgLF{GgcuuUUX6A=^ z(?+?3ntumJjx3Sz+DD>djm1a*iiM8YuA7<Hn!`Xt?@|BeRBH~3c}|Yezn*bU*pxm$ z9#N2Qh7h#hch{!YEjGjtmTr{1;QhH$-(5c4_w#gXBe*W?DkrN=8Sl>7U>`?oei2jK zHdph5Epe<X?A%iWdbegAusZ)<!qeZTJ!Yj)145bbj95Gb5R~ReBdvgp2F{ARc5*-r z$ue_8<1rs2NCafb>Hs%-!^IKH#KrEp{i!ik0f+FaW=`tYu=cfI<sSU?x%*41{|JeM z+5*?joTh|el(|2%6fYqg4((XdVfk<91mL7SaAVlRMe)2Gzg`(CsWi?quMxtj*=4D1 zxsK{I?X|mj1JkLRFzuGb3Ytzz6oJxJ=}>00u(exrTKE5q?xp6AZLqsRpd%*iC?ROk zdT<c3{>wOuUn<E}_Wm&a2VjzT_RgOpSd&z;A(*FB<^#HwD%4aYnTmfz?;J^}`y@m| zyk|k9*#2efYUkt+YB8#bnOvdIX<T3FDiaUyh+>*bWx2}S57U(alZ>->^&GuAq_Pdc z8l^HG=zrdzc9@Hmqa{xzTrtAsY!QgbLk?9}2yWZG-O~uBsFDDQMH2RRa&dKaFgcko zeoyO_42jLH08n@^d}C<@fWsM_4%2AE&?r%5ytz4rQ|<B_f~Vh0i^yFVp#UW<B$c6Y zl%imWI1wjlJ2`s7XT+}jufW&qaTcMGARdl@ZcypjC_@{F)?4T5ACHi(!n$7Q8DKrw zPPt7SZ58TU&SU7k3#`gfIwMgdqFfjycX-Dvc>1cOUK=AK)4xe~`U?a0Bz&!|opmR2 z`ny!{+;To%bOYvTW3708+6!oK_KpHWPE@c3LOFVUxNJ2Itad!?A~vUIaK>FyH}HrQ z9JFgLW_y<eSA;nseG*=)z1BLDWk7v7bp<#6*_MV@q05j~T50l}=2S;;(B9@@p4+)f z0b1$sXz%4`ycfH!^gM-JJNC9H->po+B#(EVp?AUgspFN6sEQ*NGK({j_jkNpv)udd zFPYm?mC&4}*`P@56CZlLeTc+WPQQxQcmTTxX#Fqu0@tq}LVcS0A5lHy3AddCTusj{ z=4Q4KW&ygliIOE)?{?)R8nV@R4gUSPKKgrKt8sX4WR0>bM{3<(q;WWCkJ;48pBWkX zXG6Evs?VOxrx``C7ooSW1%o;RZcHlM&v&!x>42>=p%GT_M~!Oz^n9=-FMk$I*vYZ8 zw@A8JJtqJ1)Z#vL8myoG&onrx2>yH|5tmg+#plWesCcB~@;SX4M&@ueQwNWWt(6|e zYy|0Ni5B@TvX3vOqx@VGDgPZ>hu&lF$tx$abhv&;z=>!~>e*Zb?v@dK_b2qtzq93& zRgUjQTEY*jnnvmHso!S!545TUfE+;z`?cdCOF}T1HxZIwL55fszv>Wf+JD5vYO7R( zjehBP7G&bw#7TaI1|<+`qU{F~B-k}AS1NH*4|#0=ru8Cgi(S@M)O!=T-R+M2UFO!7 zVXVhcSM+-fy~!UI`1R`~F3BPwx0&;JE3^eZ5*NAC--%+xQ$L`_{0{I<N-w7qAC`xg zqsAzjBN<i1)5H~)sDT)9bUP_<tz7+TalY4@w=;{T{N&lyTRGjh0kd;?>hZX+JmhK- zs;fGpNjq@=nM6l)w4$wP0j3q&7B^__=%OaBV>*DkF@J50#kd>wgo)CXwR!V$3a7>e z<s@0({xc1C;hLhZd>SMy03_tgx&g2>%}*pUp*z9$6ykVXe#p3M97?#8{|pgYbx&SN zuRMA2(P_Zgy0w;2%F_E4ayg?Vw!fm%Z&64udw4d(++Lz6YbC9e1?5-K2SFDf1cPsj zsgQ59Tr2j)A~toaC847#xNe<AyhoJb#{uEM{q%6)e8%2g8>fUVDQ#<!B=)jW79=i) z6RKn5O}?pC0wl2|Ip=&8Vkc>Ail+5oa|<VlqnKIorFJU8jMBoTVwc3_1p4wiKyp9f z*0Mn|i~D49rN?A)JAQQRca`_xbqO6$)^wbWIQ=)suuad&9r$FMc1j5-=ZE)}q#ofe z-}9Y=%@pD+RvYH^hFE6!BkWHGnF7wew*v?O+vx;=*~rN|POhHIBuuu2p~y*7flz3G zH4Y81JXoL^g@8en;E4bQUp!?De<6Jw@wvSx(NZz<*7iIjexW5JxX`jpKcX+RfL`D* zF2AA;M<K#On*PoEW;+yFM%Eyw=3IWr>RM>>LdzvhtF6v$uNne8!ANW4ms>D`%Pq=Q zJo|Pt>8B2=vn$%J<z=;{57VHPtsE*W^dK=bc<9p3GY6|@^wl>uM_k57n05W|SX;T@ zlGXjC^7X#NSLkxYS9n4bOTS{6^6qbq7qsm3{*(%)&}j*#k1O5<RUpQss)Uv=DPEET zR&|pF&1+z@(4b8tWfp+uu5~qpxDOfP+J}^;#GLP2MvU_nnlQ4GCSDLK^U<;tk67sL zLyVsmek<jFBp#9sT@wmV2?5(b(*n>Z_>C)czlT=ns)SZ}x)9{J6^E6HlQv!)e4%%s zoHc?r-_xrW%8blp>KXIRwDw?rhW`D`k#;H8^La3@Rc%Q*B@cazO?c5U{n$n2@}GUQ z8@MyZ3iRTtB%9(YHM@t6%~2dfm(&jRl`9%Vro#QV*rd9-xN6&9O%@&-JF63gIt?b< z0gH=V3BG5M+|g5sjrR+P;#Rwb_E!7akyQv!;ip#UlFjzEzVa9Gp?0W~?}}o))s7MV z{%ECcOg(=@uoQZZb{??~oYJ}Q*5EGUn01jI$B!U=S%{150DT^8Fb;i{CxgZM4l=yb zZZW^ozLsPa`lMi_9Xe^V9j&h{FHZD^$`);Nr5)Wi<!Ty11;3O&sQ`Oyjuq%PJ4Ni8 zbFI7evA#Akc?0$arw*MD+Bd!`wW`aF%W0f0m%TQ1{aqRw-oi0x-_q8a`Ko69H~Uh$ zKa$RvBeNlrWwc-14X0R9kWNEC3d^cjG?do|<!)(%inp|HWm$wiV6b^qhz~a0$g&V7 zuHU%zn>w(m(aLd;k|eSkOz<;(nQXE3rBE@fn`cb&D}n#ZBHc7sDbqC9?i?8_tO7Gf zxvcHApN#W2Zt5)ugV~gAQircxe$JBkHCx|+Ekb{CQMp*IIPf#f?aIJx`U*ci>q_v7 zpm+b6cOB&@Aj;-AJ<4WzxcTmhL&A`hy|N+2K?e05{U$WgqWw}CWy9rr{7Wp=IB>C6 zRMG$T!-?YwFns{-Nnp`65ylJry8#_o)nEgxayh-(_|G)i+&;H*Otby9aPdtWyUoBV z7We*q#rMgOHf{lwTaUKvyo+Wa_FrO|a=mJ|3cWhF3NJ7G9KT|e^6r&}n;RDTpOnXM zOLz!<4Q?(!`0?~P$>P(>{OUf7Xe|{i$z=9ygD5<hK+m{5Psa@p-MF`eXQ<}*J^D7_ z9{of8qh6^zdV<6EUKQ;SIW!aLnztM$%Ar^P@LbBbzmSf*4nVk!Faf;tziCp_o%tjS z8EFu`bcamuTZ7<kLj_5?^uB~v=y8NrcsUW|<P@V+c9S&T?0li`q8xoQ284dD&<bP{ z^bV3_NG`WP80wjO%%d8J>0~brsg;4E@{qS|IA3~S+}_kxl(f-Rfwb{t*2tSBovIGg z1+q}qU1%Dj8Va2S5(za(=OoS~en*EZ*h&^)SisfmudyHxZ-y)#99Ewe!%iKyxQY@% z5r`U3ij2Gk(&6ectso0>-G%-kze1o0@I)F!b44eb`R6j>qo#?}sW2faVFcz)%OhId zSDL`#(C!Z2qq=m0sX>RBqiVa>*WnHuO0W$m7f}aPjHq*~{Y8jUflWIU&-S>%P+3yk zx(jLqgwbV)hg2RU>ymwR+<z+wR}l6(x=Q%nRF0>&??fH4bo;@Ex~t8(c#F-&c#Buo zY(i=n4n76q%#G*PY=q!9`no{rTM4Bv0hq^n4MdU)`PO7;cv9yb=+TY`4m=)eMBAWR z>^qin4B8f|XtY1<=V3t^;;uv;;%?84!up{?0H&l{bQev-*^CQ#TULwOL>uBZsfr*< z$QnS9B7-i=MSBoJf(K=Xj#+%^U|3<GOBOtf%{x4cSH2uVLJF|Dp{ur=&*sYh;t!+H zF2Tke@+?NRf3vpqH5MoF)2$5jcJ(p2)44kxn2g{0oy)i%tvXPXP{`!u*+Jbrd_X&V z*-dr^{fv6nlAE~ov%>o&u7deup7is3tB5teD05;v&cu%-Q<hM-DsL}eK!~v(SxAhZ z`FpRA6uRa}y0Hvu%_uD?&nUin5vs_Z;)87`vMneTtV5fwv>NAbzlp+<QAwT46I^sZ zPQ#gwTi}59QQpLQ-10=faG|%nhqi8>O&+$-{FM87Tq}OLZvN+HW$F3|!%3?-@8VSm zhtRdWgGT7O?IwnW@`)JHTSnayX~kRc!7E;?q$VszQ!j%bHhp77m3r``^cU);<)Zl5 zvxX@bIf=EJnyGr;92f3<u}1v4s0CUBpQ<Tw6;)zBp0WhAJ5k#5xP+stkw-+CX%Lb= z!eImofTp~wXYrctpXc*JSiKw#&aH2v-{VsDd4tQX8S2oMD$}6D_9rS<L1k^uh+)xP zB@JgOuJ~=(>`l^#ENKmbmNOi2h<9$H?BM+SUDB4#L&;%JiyD4#rD`Dp!SBp7Rt)?4 zi|OqTzoY`%y5!M-(t&TYW-}XxIj0S4fI@04L}adW85@SVxPl3z(P8DUk-D0gIj*^0 zVjRE|m5|)A_)D3%SmXIec6xJ4nz!jPwf9%tWFcuJV?qohZnk|ETjXfy$avoX*NxeO z>6o?vQ`z;HumUS&VLrN)V1#yV>IW~P4txG>Ksm=ZomTNKhvDMEOZC4xe+5@yHIx;S znrovuLY8K?i~6bxX}CQV0M9t@ZC%6bw}xsNI;u9h{b*yC#~mhNvWZS^TCoLO70x9j zuk?Qf_4$`~U-a$_!fT<JN{^=$rt`(IDHw2uvQhyoUGt|2P3S5M_}V&Jy4oXW>-ewF zQr5UDRpu0XQ7KB*^z_&NAF94H8qV%}n+PINlIT4lAtA~r!yrlok*Hzx(L1BpAkjh? zqlPG>Bt-PyYqUWydN+D6BYKNG|9hV2x8Akh5BCR_d*7$*v(G-)b?sv)-S*~qNpDln zr+I|X2t$GAdDE1Xul5~H3cKTYZ7n6y+)>0jkVaArsA+3kP;_}};~}iPzpxLj<tvY{ zHno;;R$p+R*1LuHD?m6!=dv}it2jPOOM#71wB1XsLqu&hKjA_S8-zC^1`>W$$4<na z@+*sa8dzL5+R~W3ocnbCk{G;f`yUdx5O2Xk6)UAf>oMJBF-Z=ye7Ub6XB*Wm@d)Oa zAr<pcDs0R{xgh_DL2FFG;*_IJPlTgVr!T2taJWZM>4QGa8hn00{xv_lpOZU5dBOHV z#ht<uvO0X<4*MB&JjLsPz7^drziqb!^sSpGHyn|RQ_zb@xSvwN1)dAoG;rxaceHFp z<{o$c1Y-_aJnnrdNa^Fh)tI}^!TC<$wT#8x39}9Jqt%DcLIra&-@H|2ZBGs>d37=o z7QFg0Ob9K@MPPTwsb5c2KRy#h;X^;>>c=jHuC;7<<wtxA4(0s<8}d@^`*-G&&{@SD z{lLu08{UGy62s426==^W?y#6#fcq)9laFEQW>&OAm(wy#QGLs~Z-1sLsM+l`6k|o- z3afOFyVp!l`~&oU=dMS&@8KW1L*}=`kMnae&MM0E6pOCO)E~LP1q0D$H?+Y;ZR=d& zW54WnK7Ss^x(>i@`;<lkUOC#wFVV<#g6~+Qf`!M#(h1P9t!FvHInzML(lN<S0!*D2 zy<Al4j;~jutZr1HJq_rsT*oav1Ew6Fvq)>vhUgRTrnsAzO;Zqo;~5=uyU4*u)`CyV zmQKY9YwA*k${sV@kWZ~(hfCMF!Ii^d!i0_%OcjGp9BxfD#mWiY99JcKlEu5zgkOl> z?`Py+cJ8*<xl5Nb9z_6QkbK3!-&}Z~I>CZvjGL<@H`-MtlVM=pb&fgZ$CO^JwGZ{r zKWK@5`pI5Te~KOt=s_|}#am4ZY_SZIRzZSb2p`AwE(^EZkjU=N@v)TXr)=4B<b=)A zQssov!gP~%&s#IhjLDKn)Wcgvk7bo7HEHEWtZ5&7hkmp;`sz1$>w7wN^gyypRE(0v z=Lrs$G~cCU)JRr4$W_$ywZ-jjTY?zQv?n5(TaNzZ)LBC7-%V^1<dZ0M4zy5ffu=g5 zG1J7>8~ql-)@&=(!wkvQ$=%lTQe33MdA=$}s;vFV>!8#&=fIgaPUe-<=Ym{;HgkwN z%*F<P@ebW#Xm!EQetro_ZoxEA5F<MRy;-DS3O>3a3`v(GwqCa1_CV7l58ryvJaIYI z8CJQasS-F)_r)zh;`!qzC}@|pSKBnCd^##(;9OgT#D>qjGKD^^cZr+_Os%UGE}T0R zW&L}$TxWUozYja;gr{c&*#?U%_gGrd=G?Q&wHK*GOkfxXtov=z-8mow2T<xj)3Az~ zgGP2rU8+Pe2x%=6-)-ik7iHeT?5B;ZoIB<#Gp-y2P#KdNDDk_#UnvE{{rvAGZA8*Q z*^KNc^m%cYE%-=L7}6?-#CPBiS|oLV6zgMe>cJt*Z1o7|F45jP+wB?|-(6R{Q)hcT zcW#ku52-{<Ve~Q9{cdQb?;vxBa@0W6nu?mEMjyTz<sd#Ak=TV!h}kEON@n29omD5Q zgo$y_4O#qJaN5rk9FDOYe3AQnB^$_}<@shRha3iTZidgUgf1@I|GN3b5d{iv-P){p zAX~nL6l0t0Z5DiwJPF5^Nc<^&Xv3dc_hgy0;eL>@r7o!qRRi}KHuRWU**L*kVN~ZD zkr9(AdmlC)Si{f|j=YP75E=B4+xmZI;%uNJGT5ZBb^8{=Gz9ovM6{i(A$da><0Cyy zW#=_<?jb@w4_@_(*X4h{rj2^9X3o~}v^rFB6jyxiw>s0n!U-<!l@V#2e4idC6yJDr zCq)1mV{E}1NM~ZO8DMuvNig;WSPS!nrk5jhE2W=xD0Fryf3~<bED&auB)*xRrd`}W zDR?j1n1@4|@L9eWl~zM)!Vo5W$L~cU<8s&bz}Rbd9~bhWOP@+oys>=CJmWRF)!3AQ z^%s1-AoGY3VmneT5A`<$9}~Q0o=eid=J$D4DA7~1`rwzm+y=$n-U93DwIP_@&0b8( z_zP>DmC7N8`0C{G7tT5foQz{d_=RezQpu#cg3x3Bl9h)7@)erJ>+hOA|7?43WN?$k z4&$50u=l^_u^>SP|7%|Mf6a?R9~TY}HGdK@)A*LHcjeWnK6m#l_d%nSnqKM4_mKz= z<ZR69!p}!KiTTqC<{Osw4-@ha?%76l8d%zY{gU6SOwOYP_&GNyR|Oe%pzsZOTf_1N zXSB_YRdloKtJ<ShhtXWh<p#Cd+6yzP2nT3dDy+^6lS?O*#*~?Wb}8=4rF)l_$}*V+ z76q2Nk;TlI?Tr}p?2r720o#s{WP)T@XU2GdiZ7YHzE1BOES;atRY)7RY-4*=1Rb~Y zYMX(08uX0W`M+igsiyNZ*c`WW(+epW1$@B_nXi_eFO}p@lt9D0NJs8?L!-NC@|BDn ztdMrY_s`Jmeqc;Y;jgh>^E!8->e$|~^WP$97e1ltp}w(Ia?-^7h{Akf7Oh-Gc^{T- z%eDxl<Izeo!PkTDc58d=y=HNZyHAUw-D&<v)qQEH@>9)V1(a%2&*R)#cYifBr2Qf} zZQp$bt+A$E;@q%wxR+3TFl-<BJC1JJVAaauZF|R_>kA)OQLFYhU<||3_^RdaHU#$J zpX1Yz9ai+Ox;doGp4+)Y!u|bu{&_1q;wh#)gIOJU@ASBdpAZMb&LX%!7qbIJmEJ6e zGd!V|Bx?ffDta<OFgjDiWGU?T9Hg1z<V8Ro2s%9!6ME>FVm>1JCU(+Gso=h7W8g08 zC7=0yQE4(Hp+4dz-{E~xNUYprytB%pKj#&r`?^B&BPgsoLR{GUm->qWKbp+SazA>4 zXavjb5~dUt$W|hHF;9m$a%ERKh8Pm6pA2!7$|eXiY6k)Sh_O^5W};Xq7nh_`v1DPM z`};I@sz_Bq9Q5)jRh<Uk+wqL)k7gTHHupv|4lZoSI}EC9;wCeCLF5I`0YB;%%sq|b zDrG|38hjI;>PM>+{^YdKJ^SYRTe!O*Sh&%47q$4zJXlzo1UamSSbTOEEDS-)5!(Q5 z7eUjYh8fsMMSMNJWiy&RqF$_0INOy_y9?P{nCSed)vYkmsh>z<#XOfdTjf--Z_rb{ zRNy7jD2PL`@|k;yNXsD2j1a7Rhh8EOwA`L8V3MJ`D!Uk+_WE-IZQ3CnrN<3bg4~+5 z(W??q;p7)4H*)Qvm3NLX`r{nd(3PWchV^Q_aSmr_f(6@H0e)rrsua%HHw~^P?sk$u zerW(A2!O&ly75zDDlYA-e8#K}ZT@tZ&Bjand(`;{ZMIQ^1~2X7=<|Co$a&rZW3eHe z`wA{<Y}`)D1^zq$|L5kbC;^z+yhPh@OvPiFKoivWg4dOZ5lsFFNAByDm0^bO)p;Wv zrLPmTn6wjt@2lCVKxbY<9|%2DDJOI4o-+DzHB<LHbgr0h_AROEq~~?HV<Cr=O#4^A zl5JT-Dy$XP&~tB05ZH!NL|RxC_-$7Z_>5BIX#<d8$khS~Mgx#wkYIP@GVZ#*{pL2^ zAEr*w5JsOJ@Bn_V`7tEzOXk`Ed`9O3RSyi>mJFNoR1*Z-1cRi1N4e@EOdWB=7H+uU z+h|~E)+3%W7*f3rdes346s&6kZXAm?-QJZSu~gT0pE|R-D(v`culwDTqHfor#Gbkm z(Sn)oT<_-s_%+a_eR;${v#N@kiAGv7uq_3E;N~Je0@NyWNmIw7m3!1#rAr@St?EZb z#d!x(?^7~!o<$yW=2Tmj@K9CWX~Xn(tgEo0Z8AYO9pb_Vnx#~tO*MS+hJrzBW+DLs z=DR1Io$F<7gmqLaztJL5-ca93E*sqvdgx`WXQ@i<(@le&`U0hXeh$fvduhTk4D27# zpNaqq=Zp;miIaP74!F%r^zo<RTWkbXi1LUitCeh*^ntXU6f^J#=-;if)|jnhf){#F z_8P{9>?qkONWc_otK<Awn8b|lvaEhGy!Rn_0Tn)V5X_a1vffkhPhaQcfqOcYT<MAa zO%%h;t5@X%O3q!%t6U!U9^uvkV}Zu=ZPcU#rpeg9jB%xqu5@zFuX)v<_~acB!j5Q1 zcd8jrPc&#g_hgBdVWX+Vn%Y`^9LoRM$V%H!YI-*CFh+p4p{70UYa`~?yL5N>K#Bum zP|9L+e0e6-Z!(FcIje*Zh|R|w4K<g?&7aC9si_m%!bk-P8CK@C>Sy0r(bB69;EL<} z*s=yMd6;URWmmX|6+?s_4_s}dei@J(x;szBYcY<MJqCQz>Sc!xdxynWE`*w+ltq-~ z4ODdhy!pi!1u^*;eb^}Vfu(stRsM3()(4!m-R!`^lF~Qbr!$Xab|dUr;%6f<e0_C$ zD0c@8%Fy4GX{Cm4V`5ISc6E-#(+TLSJM(b;c=w^g%u~gu;BGj>G)-N!Psh5jG7~K7 zi>IodShk_tk64MG$clHL5P!p+z|JcY6p=ft5-njw*Dr>LtmrrCF^mm#{X<>cMNfwW zsu&vR=7$~=Kc8dc&Z5`VB5Af<=?{9p${N2ATfDhi>zYSxODyQ@>S{+RlP>p@Qg)cu zTAy?g#p~E+WafX&v_nu;*G@N2%e7A+3o`>egO%VLZ&8em-m~{i`BRIvcA?aZ*Ei0R zbOw)T|EbTR?@UM#7}Q;QuI)<VBlzL#CTC^TZ_GsxUB41qsSwoea1h7W6r-Z%qrr~P z$d>K}SOz?ZSy_m^+hr|*OXiY2c+AsfqE@zhSl8N78|}OqDJ0x?zK7~}Y_qiXcVSwo zrAwJ^@9SUvLz0++cMWDu>j|X;y@_VJOh6sQG;lMyxgB=Q_643Gl?1;(CPOo@Vy6D% z_T9<IT;V>KJ=B(Cn~IIU8dFC#-RN|C8fLYQL^K^Rw8Av(G4bQK-cRLt>a?+2M6T>3 zxn0lxXvS7KWX13DKY#wWEpz)5d%>Yk15xZkWV{HzRgd6pkL><ymGVscn-P4ga{g;o z`~>N--Y${~YFT9nRy@mRH`9{gO|3~Q@NUyGpw&NXwTsifHgf6(CEkobimU+liSMI| z;BA(0|DQ}NO?3LR?R_(=DAL4gyic%cPE05r`j8k$Yg$%h$NUON8gx}~_WYwwhh@RC zo~jx=&ruO{*=>t<U}~5s>U}HQd(lotJ=YGQo(oP{JeL(kd-8GBeH3f!t*5jP#>Zue z5;7-g;~X&fZ5*TX_$}_Aenwx6YLE73_cS?<r7RjNiUCDas!-o(ZjjUfF4Vwj31#5a zHs<Ev!qm}3S3TEWxVD;13he`0XThe+C&n&d$()=s<$=f4<O71P9{&|r0;-CPC#IjT zE_rj)xZl|Tp4{Y>kWq%#>L=@T#vtwuu{?7%Bh~bUy2$#jbAj^AFfO42iWuMksyJD* z5qoDoNlkn)2?JS7>L=`0%6fWVpc?pjo{;-72yo^{bh=tx<(4Z^aeB(|jBEjO;W8}s z1E)|Y8$ZWN)H872juTr24MUsStEnW6@>G&a`~N~_8DDd#DiAWs((obE^|2%Dc8q+A z2&6nEN-!3n%|a;c>M5uUP%R!W3Tiym3TiChic0e!?-?Sr;alJB-ONK?kK^-nBrkM_ zL!a2=i#ZMR!c?|f=GvP%-ZosNp%4@}^1`ND#&7>R-E|A-DP2%wm`K7fP9*i0p?~Co zk{v)re9dO6l<pcGsX!b0T`3&;4(L@WerZ~!u2A)Pq6SNBdg#5_CoiA>E8x`m!t0sO zW0FRJ{i65^hw_K_t~&UKGI7?^UCfO1O|Cv7O)Lc_B7sG#1rS3#JLFe*!qXY2v?|?N z?&&ygwxCLIeUWf+Beb@_cYxnV(#F_O=!hvZ6}?#8)l_r5E(&Rq8*l)YHn3Tg4ty@M zEM4Oksy_%@k*Mj|VO(A_JbwSX*P;o*{#L?HVdlQVbHNJeWBY%BplOWqSdz*&w9R*r zr9)gEU-L`VXh)3*e6K1%kL^Xml|Z=a-IraW6Zq7@fvz;Jl8FpvqlZe~CX6fIR;s)+ zjky?3>W}%)!a+7)Gdtdb%zqZHZAD^~T%BHpnP)S5zQS=zy-fPP3bHGhCqA=Fi=!pm z56*2?&7|-;(^qreFu3JRL;A@ZDv%2E@x~x(ER98^b&*Rj1U-DK+19No<lQhZZ3od? z3fcP4qdz+L>slVqO3sm05hvHsaTQ_b(Y|alIb7$Z+t5nHKN#Qfr2axQX({Nh!$ARG zv#u)Ug9ada0B)qiiyl6A6S~^QnuoA`Frt1S(L%9awm(w}7s#VmpBi>Z`>dc_rrI;R zB-r{ku}1&nMWXvi4Q)cr!G-<pBVN9dn#3<P${?!RZnE{R@y{TEX>p5WUFzZ|xxC;j zKGxlvOu+-BW1RnBqaI^LMC}gsj)lF>?=*)6Nfn;${Cz#nVx!Bxh=MqE^}G5LFz*mm zC9mkrvvBVd$qu*zvlu{-3yCH<78fZ9<pKZQV_Kof`{z3?eYKW_v*wps_&-J?o(`_V zIhQdfrP_}@yxJBZWY|dMO+b!D@`3MuL*kdA-X$uYulVg-CO0&C{n%Epz?%0RmFbEG z%$b;D?9N_;uixv!$dV~rg6Wd&97GsSl+)20qg-pQCLyhjEQcWyoYvAk+-gE3wtx|z zj^4^aD4Ilj(Iw@Tfot&T%>k`f_&-k&!A8T$V7XCGWzk+@@S9>bmcq!j-S%>K^Lkq9 zz5|FYdAC8Q8?Qi8!U}`-Gj1RowNNQ%QC%i{#hjWk<*&%pe=`tSABd|*ye_D^zIDqM z>WhK}da1G1njeZwGa?BM5K>NSYp!mxA-`6Dh7cm3#$@+O2HI6rb&^zkMP?ENZh+^E z*L>d-JS-{*sl3yU*;>?5A?^N=3A*Jl+*aH~sYYq0!HNPVJrv~WA`;N;7WGEO@h&sV z&zq)EdGFt(E&g4*8#Z??&%FoxEg$=N7M3_(R4&2Ka`R<f0UL%tRPsl-A?P_vW;*(A zi2_ya;X83iyWEKjFiW?($2t_UVfGpj1jYNE@V)nqf$s(AYK+SycJ~X#?Usvfz_d!X zWAL7Mv7*z@-u<5^b{99vr~#h%0pF;9K-aEuGq5)=+akz4LS5_BH*a~1`rP+!)|z{t zlRMwh5?T9mVF*g$>~(Jj(lHy!IDc*{qG-z(R+}i+)JsNT?*mLSw@B_1GVHfrMMMa7 z%Fr$R`4E&|WH)p@)aaQ8?u&Vit?c`qAu74w^9X#+6NdAZjVgswIO&-A@1fe@1LRKr z?^D~)4G?YcJqo9{QNW=v*cCVwM&S>I=<eI93!h7-ifTQ|aAG&z`%8Cst9p(uV!tFI zw|!<(GbEqZ+I?6G(5R8qr+un<+66I{$+CB=EM#}Vtkzg_iAjG#o)8(RI5{}GC7cmu zaftyZ#P<ZFtBN7-SU48$m-Orfez!=M6$|{(m*k_A`=D<pD;iNG)BSBH@UH=M#hU&i zeYCDP1&xd`vRIw`=4$ANOhf(2Vgl@V9$MJX**@VN{%sy>uJzwK{>x`r(a_)cd`#zB zjwyHkdkh^?u}*Ty)lhQ7)lfKwIkN!0SkkppbIK+LnUj0&1k9sL_etHrI1VPnqg4)Z z9>2>S=-=-!H(25!o)LSGEq2(20y{&dKQ{x>pG)msJ}(nR`}0-QRe$N~O`+mJ0OM~X zUGmIH`jp2lQKKuY1!jRFIJqZ~+bZQ7_{sZ)Sgn_uSgmyfMA{Jf&=}$5wCCyW))}(B z4!HBZn8LKQm?q;tZo%e9EUvP+4!<D(B_qY87X(5xWX#wZ2s1Wydh^^x1Z~R)Zm6yu z?q#H~&&GdJqy*{o)g_3L;<Y6W^AwKkF8$eyy45)2+^L(5e|}GrjNEMWdcg^No3qxM z2_k(QRj!LrblMYjb2|;TU&XI!L}8p^RO=!lM#b@V8cWcXl<~4jxYyK1`17rV&6(g? z029xcO@z~~9!#Hv29|lXU(|SEE^4eXV$zS1Z3c)V_+Gj*0CPwz0M^WqJN||RuxL?n z^*Cjx!QWhgzJKoYV>Xc0<_(bjP>9nW5bXwlX!mbw<UTi(wgRL^qTT<dMw2YH+Kmq! ze6q|#<}(gd1CQ%!B?55W1sw}a6>ft28$wvzp->3X6`JhJ92)XvF06j_{G}-IDUuHO zxp6he6u|dx?gIFpDIVV|i)sC^S$GjK;99k$0#JBjudqqbgzwjnKU|~6<<hwV0bWpT zY95T4o(K1z{tskqhBxi00wCioKBFjig**2hh%qH9aD$6Lib8~H0z*_eU-q<UWsY^1 zM;qsi<w`jh#FuPl#W!qb1!b7o<I#G>U4u0T@4=8lxobzjsfy4im7bbX(JrOk+~Cr3 zKv`jJ=pJ#p<#@tM=ec^OrFdK?z%4;whYO8`ub^V#9ifi?QB2wObPs3IccxcGNNFp8 zu^DC2?Wyq}{fOZVrcFCKMUGxmnp&OHtuLVUGk)x=?JKC`R5EGxv62U)reETiS7g~t zz1!PrTrq7mW|UxQ6Qr3jA_Ts|>gd)VA~6Nb6DVfK)R3a?pR?b`FRdXHrgNmHB)a`> z$Lk(c-JV`l5q8{C_RJf=v#z;vG|Z4&?K1|R&*thD$x~7VAnegPw(0?&$zJZ12y&ZB z`DB9lVo2nBR4!cSZYk^v^rV3vPIU!TUK<-28gWk^h(4%E*C>Rev<l(L2FSrT7E=@7 zbY^|3Q(0!S?D0sO<4VCeSg>XYQ6Gu)+6mi|_`3lxP2ofT#9}=YMs=tdSMyz<%Cr8P z?}c8tZajiBbJ?w|j52gpznTFXk8u4E&&vUtmA;%D4`!;SKk&LAlP5S)SB(=E73tT; zxhRd!=bkj;KD^(2m*t%!&@gz|(Lx!|v-i>0H9p4^He%S!^%$aD&WmVFhQ+gU>l+ZZ z>qCE8)1%T8D&WbVk?IN-E)yJ9vp!GLSTI>{0dUJx+~W1Lt+h#KdNfKQx9PgWs}=T% zJd;M|&mb3PAw}=Ut21Z{c9{3lV$sT>AQIe|w~zV;@1TME&oO1U&?(HKd1qF~NTF50 z<Z7{N4J&4!ar`xocw_0qJbOtdI92xQWby#adu~^+KKpENWt%d(V+mQHIlx6Zswv)O z25^br2t+!~8kE`~{p-E%dOytoF40vF;1W9kE)l&%$TNH=E@HqOPuj;&?5KTEiZMqG zsEQF1_L3c%PLniB?ffOaWes&rg_(M(q7?CT;wSXaB6$>gy*EVvmh~&wRDhI926fp1 zbfP&xCz?|;Sv{eatv2i0svS2QRK5Qo5lU<4r8zCCz{EK0r^&*UT|ue9l*X-Y^)T@v ztzA3-Hlww3hI^Vn3CXFL6X~7lG!D+xFKKnPayo1$WWP#!_T5?L{@#^z@5m|V18=C6 zwU_IpXbL0V*dpUf31tdn8uuXVq4z^8YrxoC*XNYs^X6A&K)?D;v;XZ^Cit)F2M{yN zRh=sw>;2uY8_Zkbb?25PbT1hu>!m9BZ@2I)sk|i6t$$h4z2tvc(rOEl4+6`(Z>`O# zr#J6%1zL94L=SF+p&mS`t=;cw{fnX{gIN6*GF$!5I2TaX!3^C>2Fk%yCH}P-jebxp zF9BGTH@Ic@Y71{M2lU-SBu?qR)2m2x3ua#pYJ&QuqhEg<AHz>?`m{#>R)mUL(-*<C zka+6udk#o%M*|5^cV%?#Om9WG@^dL2RK^)pvBO3?3HO<M@Rf2yqW<hExE@7T!Iaq} zeNuCmMs6n0p@aec&GyYFESv|CaSTfIif4Hw_x)hWsLt31L4aAkzp~xFPK0BDa7dRQ zM2V)73HsU1u*Y}TT8-Z^8{x7osLR{Bf3cDpgfj_e?;WFs$FrZhl5EkDIU5N^4r_{| z_eEF4qBWy%niPLd6+w{Jhe&f^?Ej!eBR$Lg<9s4DBM01=71yOF>2sZvpk7BNC%oji zzHdQNr+dK^QPJ%nb7zKfS4UHi82bk`wL!fR6m~=j+FIP;5(-Dq>cm_BR~(gkM_~29 zim)#(ttupjouu}&ls0PTE!LQe7HhGns#h(gDRc{y{Z}nHIUfPOrV5DXIf+0#AEDGc zen$QpxR`AL*ybjG-`*QN-MG+x@g68{p<M)RrBF+hrnpO#GLHHl1ldG28h98BmmbBt z(`#=4P!9%Xls;+xvtUhk`F>hUZq54}<lv^d%Y@vXpF*W6)cnA!{H2WUz6biY<h=$) z#=`rD@c^f>2D}<=Y4innwP9^(D?-&ar>%|>xHh5xxOed=?Y*aZt@0=v_x45DjGcVO zUT$u0Lng-f)-5+EWXgOKCwjweW7Slci*$v0jG?Exr_I`!l8Y3Q$AaepSCicYQ-{7x zpRH>v5H|&cpa{-YGc%<#n_>E**Ou5HT}#eY&^LF$34xz<$C1+?eKtHSgmal!D#sa? zt2fcs#$sGbQh82zQwPZnpwupltuy=g<{d0>ah!!@=XHy5m$!bc_sn+v;A^&()9B*b z1^hOCZE<$iS)Jh_3=KLo8dtHVA50fc7)VZuqIzo~HEGV3<|~|n+Q_mN0-{`oMQX3@ zs~F-egEEeg`F2;eETX9gUIaEjbP76J)CzwH6>fXW#f#cOkDnX8;3s*q`6!bumC%t$ zJKe^snh*mSwC?vsZ<LmUWf4b?>*+R!V1hAU01TcdF_jp!eH+uUk~TuyNMH7EPB~1m z{V?cXO%d<Jm?xD~S0gTSzXv&y916Zp&XGL5oH>6;1m#VGH3z%v)G$_796W><=?<A& zrTOxvHR%#m<9Eul+sQg$rsb92`Mf@Hv!5~--@CMSBwKhf6sZ^0bXT*^*7Wqa$CA=$ zk2`ybl40@}tW~bjKPt<)<Pewe^>ODzE<){Vd<!3$7A)9O#)Pc!PDk|nqQ2I}Y<CTi zcF-?EskRy)6sP$#B6?+NUC-h>KEZwz)j8jm%?T0Kb{Kbb%YZRqRMwSEpH--wP; zu~?n>7MSKsj6%(2-4+K%)y;xu(e{}MI^EziN61`coNCaH2_PY!3=eo)b8Y#3cD4du z(SUm&Oyr$s{~3_Le05i>k7Zmt?-U`%X&^NDj};+L>+n1~9tN)iU~tMg6XVXbXS~C` zj^?9SrTvlL@mJ1;;tnJ5+V0+<Xr2IxKgesJvt?m|)v^LJ3_qo$P4b--vuG+=3ziA< z<U7q~Y8W$(4*WqR97N$R5-QN{sNupP&uGR%{lLTaG)rtR^-v0K+IqL}-P-Pra(CnU zSl_ktn<6CUe8%-deru}_NKSqMqaVUB%Bxg)%JEa}=w4dw4A}d&o~U2{g&0<<lURR& z-D7k%{zqd$Y^djpb2<rb|2rwakuhM4&~Xm`JI%jU)$}x$@OhV+>qlqUuJOyHM?Ax3 z4yJwB4czU?gZR&I`P`f|2H9hYhvs+EsmnjAbfRrCK*#p?%0C)+aw!SqeZuD^&2=0# z<33+|l?^yXC&$q01V8JX#w3b)wyXCh>lZ573C(sx75F?b-{^!8Oqs!G)1t0#bng(U zEa)scd@jPUVlbw&BKj?UwUb1+9T0VF_54m$+Yqken{CR>g!_7*gGkpZsozx&L7Me@ zj;;L#nN}+4n)NqNtlgwTwg-Uh>LRL8Z0Cm0vk7SmRY#FD)()CH$5T|f(Nm>~3V`e* zxNXgCWd7BizdW|S^Zca<i8UX8`A}o$sws(Q1b!45Cm&B({T9ztcB4)o$PHhT&Je5n zk+e_sPvO^Mt%hdg)ik8l*DxJkDTOhO@seCRy`Apv_pyJk>&JJHt%IxIM?fDL4$cGg zk*cgSfIf2Iyt0;agobgghTI7dNlAGQSQf^U;T7ps!8aSJw@Zg0C%QdjCjQ@;h#Kim zbT`LM+{!`<T7j;#MHl{^iF$q|`9z^SC|W9yEKuqNDWv}6yZAG2^X9?BBZyzO)4bRD z;BuYH*PX8I3VVWk?2w0K2vkC-g35igP2tl{C4)z1abH4}@8NNhWdJ9+hsQ}i#n4zy zL|#Nvd~gVt45~4Wen;@nQ@;$<Y{h__bK5=0(nAF5C|03g9&J+u8gn>^RhXCOQhJ^j zjt_Vzb!-*<AG*ucV^gNi!4jcl4}3%F?xs5wW1<T@WN1LU56^nYl=N3o+0V?Qg``=L zat4U(XNS>3kQ_NyD<ChAMSFhsi~#cTkhe6<vppfHH+taujr=GP*+|JHIpE7#3jHDI z?jN8?hl4DI`4D`LN&0V&S;tnzADtL@_B!+%CyVRLk|RIg=N+fH@W$y#L@p)em6U2~ z!LzKkVtY+PN^FGU&png^pE;eVGzb}Fh*01=q!WeI$?@U&%|&!o-Y^SWc)e}~b>fts zd<sDUfFCMPzWM&=&rP6mb9VMm8L2`|fx`g5rR0VIBG~~s_}ZdpV%Y%E<bcQ4CjZ0M zU=9Gb=8k*&|FE?tIg>179dG^Ny>B(!U$~EB*iHdKd-S|uF{M{{K;j--?}wn)$U=#k z-Q@=N=$cQEng4+0=$anMnN=fFPkdVH&N@1*DF2S-CNzYW@sutaICDH`-zUK27Ra1e z!0()L$W9f$Ti6Y5cAu~P<hyYGS(L<&Z@#w2Z(+5J^rQu_(7`r(rFf)A99(MsL}=1A zd{w%ABl(tk2)?L%n%m}ea5xV?E*R#QW<&O%3##Dar8x(=XVm%9#owPPyN>Rj(W-)r zTVzP&BH)Klqn`$6$4OE8c9_#Ju5RKnS_I>zr|m`ny<u~*r@Jpu64WhsPsWOB$YcQe z*u_f@3n4S=eB|Pv$`sN-M`pD8*u^a`<Tc*b*XXA~+0#;#A2^R_QgEl_&bO15T3_?= zHqcjF&RmA?Jlgr2jvZ}m_@O$K)5{5;SQM1erELSizj}9$ItX>!zu=!cA4gq1ecS&J z{<$aXHvDvO$@pe5pR}Y(a1^H7^G|KXuRc;YZXa3OLLTgHaE<%xT8LJ|ft?-sjKOCo z_fn4pd1mb`V9TL|t9)lxok3{(DJwbNbePdQ3u>r>S2H<87x1+7U48+MRE;=3-%GXg z=#8TXyH<NjBT~<0reYsQ9`M7N7eC_Mp=)WdyWVOSV4GiJ($A4{Mu<Z=F45WTQwR_K zaQ-kl-#>fneH@}LB5F>&t}0~CSx2XC=di|Ap5N!yk;D`4fg{&cti|+aDs0sA8|<^X z<HOVAX5v4x(}Y=B+4s;A<a`nh^^?QBlH?k!>2;~^!h`a&@w1Wz%t}3<EPLYBGn18y zCMR!6H~Ms^dA7)omi_9UsPf(rDecA33@024xIO*Y!#|jbsGjaQc5Y{D^#SSlC_d(~ z=&IMU#WW!pBUN1I9@a<yrmfNUGJLDh*0{J-CeQ;)z^y6E7^>3Jiz+i%gG{@1vf4^` z$_**7U5Y<(s7@!$Bd`C$R|IzM&F-oxY2p;#EUT7z?c2C&9xh%U+#bv3%~S#-o4tdw zI~Pm*oTV-Bt%U3Ji%%;L_<Opj1&&Ck2=lgayW0e)QS3kpF8V+94Hjr8Zk3fF4+l^j z%|t)Pu*KS_Ql9ci<cC?sE$J&wt6ICVzQf}&4F+!IH^E9f-tVCs>99_3jIWptMpD`y zdCvrK<b+H2c3TdS*as#eB4(?}5J}$}O{QguF%$xyZLvLE5Xb$VG!%kkW%er#-3nzW zeyl^W-d@9VkTWc*e*c_7RMDLe*I4hp&}&Dz-2z}gKOnhR(If|Y%e@`cDf6!^QV5Dc zF&&xMxHIWKyUG(OXH;I9qi2AquIKzTvHJ$f+kL%hMQe<qzgm2MecqQl9Flhg^oVsm z86%lGx<YUs=GbN3oG1_67;$&n?e*U-51I|%e{S$?EYTORNfi#Yx?n<TP1&TR-BrJS z_}Udx`<qSb=_Q_yZw2y@OT6Cide{)7(VFpnDzDbu7MIO>f)OctJ{$M+!N9a8Ja*Mp zr;93;ZkkU!z}Kq{3o)4wzBoMB7A5iKYias;w${5ux!nhJEL<{Iz*+RK4u3YVXYJRJ zV*-8(2&y+6ReCKy<!OKv5>QPDXkYu{m-q+3#1T-DL*mKz7cjBW`;~Hg5N|>vS0*O+ zV7Bf|IhZ4a#a$LC$|Bxaj1l%kea#Y=$+)5H3ROyn(RgDtYfYmiq@|IXMu=>uC00)$ z;7Dx$8*sMr3@Iu)cmeVj17}FNGFs4c8EZD7aW!Wgd+7nyPlvtqR@139t&os@jl2g# z6af{Po`Cf07yj*t=%<C*K}DU-y7DwBYdscj>B|mpJUqwv#&l}+<0keu`2wzdRb;rf zqMK`^Th4vsGaI<=tsnbH?6Ozp49Jz27E}Nlh-<~Nebg_fHf=Bed8UpQx<qVy;qK}b zDRdfeZ3}iStJ?P=OB6z$npdHj^J>Ym7C|4T{@Fc!5hF7PbxmW(b~SRq8~@(Dd=*r1 zg(*pD16(N>aHS&fgpjT40$<aW+PqL;1KuMINmS0T@0w_xEHli-h$hILZzOj&72$SG z2TSH~m-Vk3K6u8A<ya8CY%EyC@K;H4*Bc7<vS#L^he{OOYJbN|LT2P1djor#ZTIK7 zVJRK6g+j(ir2E)IRe~jV!@F#{s!4){n$UQ8A=LyO##Jl#0w>AF2RPKMQ`?-E|2w9T zI=WhHd*j~fT~g0kpmXvpqi`ka3yRK!?@z$!I8sr<Sb-tA3_2<-c_)>P^%9;k|6Ee2 zVs6c|of|i=Vw*PZdG=nmz3}$_E=QAldm}ORJRot3`fU*EeMg`vftpR*2cR9)d{mZL zk@mC<1!!LIC7iYR>bcyf#+LmS%Qw%Q3OoM!Wk%H_0KN9oiF?0=T5#(9<K^F(%5nV` zvEa0I?e%s9uakNQK;k0+Bu=x{alktYWO$bEnb|sS<~a^un;jiT<b_{WN6psRra76* zw#ExUVV+(=GZ0;DeEs%$s0fKYUrEDv@9|zaitPfxPf0~{3!|+M^?Crt(9tWK%Hf81 zw^tu`%<AFCgzQH)4vdXo=4zl!wQYlzlVYF8VLF}NcQ`QfEu8FOSk4Op$jsqcATp?6 zA5((<30c6#U*OJPf+rYk53qdA`)czHftNU7cA1G(7Fbo!4sMF3_Du1G$xGVa7`Xk{ ztra+DNO8m0N{Ui(`F?IoRv(a7v9!wio-ypFfyl6e_`wpz4#?w$_@@`=p<)xZwwi~0 z&GG8<wt+ksc&ijDkJ(k%U0GNYbEi~;ABUt7EFOFz%n-G3oeWDie(h^wMP-$UJ)`o- z%uLc&aqsy~SkNxr=(CS1bM6fd@E>AYX{Iw<7`bz`+C)lw3e5Tuo7R+4hyihg{Spvd zCA%epoJ^0}3Y->w;Txym+yJbkTM_qDL}OIb_pDNLn=~&wY%@U_uS@$ztp{eK)>2wV zIvVLoY~cU?&Hhn>H*uD<48Ss!5KL}N5}&3iLnJKA8;Kgbr9Sd6z1iho+H|zG37hKR zODVu_F|W?P5P1=)<y|IW@-LHA#=E=A%AI5p1<qU0fI}w&?Qy_=<|!5nE*ngB9<G!L zD*R}nwQ$eG-CHkyr$*=3NrmmO!bH!1#y$g2FLmF3`?JxbeFSLNNUdd(^nX&!@6KC% z0d55$2A9C_<wrYgA<**^)~`NsjI#Ku;I#as{R`Q^`nn#<B=>l0ps`v<?SP>88h)Sh zipxpn#_}AQ$-)T4WFh|I@|;C9%$={L<@?!5uPf#D4&X}Nkdjj85|^`-CA-LePeT$h z!k^AV^f=dUovE8Q4(G@ufp;Yr;-@dpABdve`Mg@b*B<ryQrd3-?`}X!(o63wyY~3; zNtGWUh3U@hn#)&q#2reOTfY7}s(BYkvtEjd-7Q8X>=q|m#;Cv6`e|$zw_Ds~seaqB z?zvhZUr8>xV=p@8#;M}9WPzYeTnbVQMP|xu*z65fA2biijikPeuXvpB1t3&^sG2_a z=jE32=iLCF^`#a0F^0ZspL3o04sIJrUbs2{<*wDSwICzQoE-DaiRMc{YJrzY43}!$ z`#x<P`hPWyyL=V-H9u9}KHO=f4r@+y4{N3s-aXKFAippm_v3Y*O5kK4E5q}Q{ze`1 z>l8QY%_1TP{17DKgi{twIO&vnEm1kv*jh`-YUEiWwNqoFyHg{r`|QEAJ^6vboEPtY zO#&y|7#wfG$EX%-UGx`j8f_lJ8lXr06OucKYxl=ix8b#r3IjLbx~=xIRMV0VD-0pr z6^5WV)_=(*1<Tq)4oQGqQt$%eqDwdJG#vd4`}r*`^GOng+l@sN1LD75`sjNsfs%GW z;POJPDRzg^%hyP3?B%UjAGkw3c{hNEt7yeylHt1g){c&`9`_HKyfVDb;%{Ah1Ap$q zZS>dSNbHR%@e#6P!sOet$JE0bHCX60xLEcJ(4K6dJ=umJMppK6^s7<@#o9w53CNb5 zl?TwCobJS7INIL8<c_*y5}@$ET~(v@*l?fn>Jj3oNP1VUHB<6o8t{IaA!wR~{R=v; zSOHvnSOI}l%UQVt?~kDq2jHe6g&Pke)ttvb^=bzLC;eqYr-cc3=wFqdcudJyE5E^{ zcqHgJ^%*Wg-3!d?2Zms_H?f$AH64{_-8Q)(4~Khw#Z7u@(M}rWc<7-4)a54fQNY|u z&+XeGlg$I1yhP_o1Gg(Q(KCHyjogk}d#k1>a4@j&a(TDC%DttI_NeRN!j^p0z~7a3 zACX|eG)9k~Fgq0!mVqUN2XktAPZpXn@+vP{5bhe>xW;<wo9eOe+wz6~s{YxfQkDOP zabd%1QSikKKqjREWD<=i@liEECd~k3k|3T;S_i;KL4Ztpnyh58=HUEV*K%%FVv0=U zW-?ntW#vPggnZIJ1<R1epZh2ur-4p)|79kkR=R_^8N|lwA}Q@4J_BXfl2W49|M-y} zIB5Rv32|dt9R`N#x0v5w5oO@K0$uR+lsQqaUJbfxa!jqWl~pd;Oh5p41gLX9^2Tu2 zT1!KuUm)X*5b92Q0d8*bA(wd8PC0rrVTbdA<Cw;rhI$PgWq;$D*q`sH%#vBBh0Z;t z!$BCEji=G0K>CzoT2<GiwpobQTx!bl`IrcaGhc39wOCj05`}#vK2s9=_OxVhaX6WJ zjSF0ir0NKeN;BR^52_n;t<^YIa8GEav+$DDxVlg2>Zg*P*+CQ48OskzpwkL(h6#j> zG;i53cBE6c7pL5frc$>cpX3NN^m~XxEoHsHLtSB<x0f!n*eGPyTog!}zgu{=GJg_m z45~}ZuGqAWdgk`PU2lJx3*p{Vx)XUS)~B(Lx`q$zI{TxU<eTZPW@ly>R`W=!TY+)R zW*hzssJg`eC?`;-tT5nJUG9D*oD0^ziu;LLqlI+8_Tc0fT}H!;){MpJ!T?p=hKy0R zsG2Z$G4u;bLrr9n;HhE*PNum_aW>NWzI%~T@UeUKgZmWgo(^_pd}%@#4})OH5?gp> z)EGv8cA|d<t&{_L>rh@(++3}yX0PFZ2dDDy6~hC58?UB{Jc~<lw4LRaHR(rmjx2(+ zwMIm`X+ycwscC0y6m<C;iMlGkr3?s3N!nN&3JWu5BGA8!y83HQt-+8CxdA*m=%Xl7 zdD1II=L&!`X$whsaArZzm-d{sSzT3g|5pLoziL2|n@!(F2ZVk~Zg?09|6tB6LKBpB zZPlEri9r_S((%l7ppr)eU?(~VNSK*_Cy`@3e-4&7$Hrv-*n`glRR-`v5hs238YKI& z1W}Xbh2roY-9Q-bHMPcq_=eZ4U@dcIZf;h~@UqvePi-2_wu>K17G|W%xE+raCh|;0 zuhe;i<TT}?C9}wWGN1jv>Q#9RpBSK6Ts}m8iysg&mi%EkEBKh1{WIF9SfRJ(;58Uh zDwl}wV3VSD#R=gblBV*ZPQ@yq>M-!vJ^sW-SVDIocavCwnrNfOE=mjDfwK1}VUjPW z`!R!-9AAAx;#q*7afZ=+MJ;a;^kq)xdT9_vCV__-yWm$tn{ak^V_Xy48S8xrk!us9 z->?aIK_}2shc<Uw!FnUqj+skkg>#r8rTW$=*f^PsTR2Y;kKFVpw~H_hSP+VFsg)l9 z7f$B>JQv{8k2>`ycmxl|xsSw)dC5Gs0<OloTH(0hcE9r}0NR}joASn#*O*0%N;@OX zOb`|TiR$3C8zM0QEW+}bExl~J=af;-YI?_NKj#S>=UGD0&hCe*wEJFmAM8vuI~iB& zouTCh;vUY7OO5X4)fCi)2W|FI`v#r%%mO+IN37cTstJs@3h+0ZUhJ8nk9fXSJ0IPA z;?M?@E981JwH+dp<eIdxioxYLHw^sMtD(3ci|s0y5oy%hMS<ZRRd)Wjm@LZa;4^54 z(bZ=p<GH}dRWfL$(NQ*}gqwdhr=AUqQw`inc9@V(ulS;T8@KW;qOu{gX~kjNF>Os7 zLHg+(bUziA=Z$HsF`E~awm^P0MtH+lnjPH6Lf%aR-PDZPf@LdT>pX~lrR#WUD4}=8 zd}Fj$h`Qfg@q1l&;dw1w_2p?`lP+!jbeG@8ODAS7x0R3yhW+YKb713Du8rq;J^0~2 zOXjF`8QN<k3Fj8V#G#j^i&YowcV7%DSV;zE>Cje86WecuIx=%vu27FK<W=9A1{?Qq zk&5QU;z8Z9<QFwAL)Z0Tt6W0a1OEo8mR<($<~H`xQ%=b0L3f2Ja~;w8FtR{vfQui` zky_z7QdyBsD}W=d$8)6mKdtZ_sXoAw_5mDe*V;p|#do5DaY2qQS6J?YWs(!2gD-wR zoB@>O+0)Ya?jrlbaGa>`em(CdcAns2G0)q|J0m3#WB7`~kzs}$&rf4OMd7PE4=J9$ z1t_j?k=$3vb$lj|d29K87F6{#i}hgpw@A-y@H~tpcCKJvw6SL|)f<T~iWEY|8HRf! z2NJkzW9MS$MFHy0&Enmp?QV3}th@HNkm;XEjf1c;NtZhlS5PZhh%pfzHqU+nfZiQa zMe!ZOL+`AqqF{Bl&KGy(?I#|MkI6;l>)+{K7`sf|eaR(^!K?H-ypR426~dQudNV54 z%SuDwMpb9nL3koi95P6_&?MZT>6r~&B{Df@i!Zi(XcsjuF=q=DTNZvS3iHSYiY+Ud zMv;IcwXQAx?hF&f=o$U+w4Ad&z3OP0U9w??PD?{Un&Y{|5ZYm2(~;FKw8wn=+BkHp z05*!k6P;E$AnlzY6hL(5(S2ij>kbf|(e^3>`Xa9w!OtL?5Tr1m(wr%~yXP3z^Ezl1 z#QP4uI}IOf%zwQcY{X6Kt7rfXv!*YHC49wOQvk}qc4EH*);j6#GrtW3tf@JwFd6{X za_?qOouk>tZ|XVe1)uhMltVvwiW&N*ia@6a@1?Z5P0Dr{LA|Yk`}aoE|7BnS>E_N* zI$%nWzMbW*H=coYSHbFw^bq96XHSVJu}|D|kyes<|9--=Tt5U|SJ2hZAJu3L;sgpN zf2mtAOqzi#j#feZo#HuNDnKRZN-~{rYS)U7_d^*Ve+C1YuU}C3UP;0XT7Ni4!RV2l zMkki}A$@#dWdE*twF8GReiCCv(Dtkx!eI4`@fSMsk^R6V>LZ^DWfMx>c4{B$B>693 zzH!L=(=_u>S?Mp1<{#ZMQx<jyv9z8V`we2jCD63|0!$IdSwCcadN(YNS=kdj`b{Fu zcRc0u91z804UJS&=71=!h+9j`6O+;_-*Q^Om!TCE?GRiR-Uh((x$kmXUBpE}QmOf` z$9CIW-S1a3CgdH|JCOfl@W@_RKr>;5UHcmlqoO~kL@=q^-g;*I3hH=j2<{;ZT=)%L z38v-_-ntRmSYR{2UnjYtp9UIa`cGR`2|mgY#cQkZdQ_M0<Dy|2wyx*CZstTRX87Dr z<Qe!MsHT`}2D>h|*I@0c{og8F>HU89hO#ACtQ2|=2BqsVTM&Rgg6mw#&n+a-#x57c zgBzdVP&sh)Blv$9vGQ#Ho0(M_(!_s-6f9aJiWZj~e*(YJG9Kwfd)y8P)W0Jk^U+%1 zRc8Lo{BCfIdrd84W%GfZ1No(aqqDHHUxGaQ*eM=-4N#e79#|cI@`ubQE?TDO>7u`- zcI$!ednlc9gXD8uJe_C5-me5kgu~_j`B#VW8J@gKE{X>AH*X4Z>VNZuJ7E$kYzsR6 znN6(Q;d-x|sBAC^4>(loiT+8JKi3b*pNmpnJAVS6xMv&pYoJM6ZT?>1>op*_O5QNa zH=^I6AC92u+Eo2W<Y#?5pm*|9I3vH2>V;Do|HCJSxIZI(vjl$kxvPAIH-IM>(7~G) zaSwkRNb`kfG;tG-uLCjv>`~oJ6aR=nJpIG4;j6gJb7R+LdVPko)qhHO_Gec8E3Ph7 zdzvW-u1KKr3+9_G!Xsio882Jdz$J0TTjwo7jF;y>!4qP(pyL4%rpu~|9XHv`KV9+e z-HaEo+Tvq@WW{6oKQ@=G>IeLLEs8Bh9CS9c7d1X#;?C&9uN^d1&TdG3abzzqX_e7} zapc8l0#NCj&SMZi307h8#sh&f{=rOV3fA<cdn#F1LjFXd&Y1$k!&v|PceH!<EsIT& zH6kYcg*_Wt<?C+{x~}WZk!~y@?`XTm$6TYfphn~kygzp$B2+Cxh>{7SC>Gilcixyh zBk(QSR;xmEyWn0L`t;95_}^t(DW~&Z?r)@ClOpltjszeDhSBsohSif+hSaH7V978t zjp^UWTP-}bvsLfnV744?dD}0mB6}X9sE6-!g!f1E0GE4kgd3nfef8#e9YGEdR&fAf z74ZxptX{HZZGR9IEVpxX0yYfSPj%XXMIh{ajc0=AxPw~_-$xOe@Pfz}jke@Jo>YBA zt7!IgXMva;G-<+&HJ|(d<$cotc0qJ<<O`;s^mbk#k+=v)AE11=Vo9rKgH#}Nl-fi1 z#bi^JKGD->x1g{PFHI(lsO_EaDGss!mLV@QkvBVNsM%>uW&vsFcMvQ@Bv#<QlgB4> zEoN_g0qm0dpaALf{`*Gb!nX8nOHv^<cTv717n>rAE_@knLw+P{H%U*(fia<G85Mp7 z+)cTsNc;rce1Kk&uFqz!%3jViLJ2J%Qp+Y4ZWHa9Lr~H~bV<Si57@t<^-2`j8QukC zq1Li4Wq~X@$zq$xPRhh`ftiyw1*d}?@|+habtXUhTj(>{Nwp>E=J&nij6?0_G&J9C z?#41~oVbpqffu>RiXlGlGh4a*?$jhq{~)sv{kx=#n8DtI&|aZmVBvFfckArp#>-|p z8wJIij=&|^#8h?zHyS8$lvk@D_&dKVvsT_Cl#UNyvM5a2`6BScvhHBVwdb%7<;2yK zX>s9VmdE>DgY)7B#yE!znLQ>6u{px}`MlzOF?*QecV&6eRPF?Yk)pJe7&*g)D`x_* zZGm~`IB+b@kqe>boEgaifjBag&`ctH=AA=`duu}^w4A_r$unz3)iI&#<a#4+JD`H< zD7UIzuNPgzFfEoeIT4WvfJJRCkcG<Cu{Hb?b(cv^&i2mNl;GGHMT^jhZx6^td2W3; z3?)e%0ahtBlh*56wj2-UyJ>koiVR&hryS3J-8eYjT&yTa$MExaOm8_Z3000B1ldOQ z8SHv|tsU*HC%6BB$7q0m<Kp!E70&CSP<e`@+fCfj)bYvLLPol)`0kNo8tuW4ADbQl zemb5L(+YOtHtBqytuYePY)DN5L?!<*MgsC(F5MFd#h=k06UC&k!4jIBp4}Q#K=j_= zdwrEfHY$7cYTVm~c7RiSA^ovPO!9tojHZ@IOw~D#W4|$BgvBX_Iu%j)bkwNA6>Tf6 zk|)jovRpY@lI!d#8{=Q&zb9s`NZ`5%3rrHdl;0ii1M1Zqm9L<YHuM!sIX*QH(v%N? zE0x0>`QA45yg69yAxT^Y#)FetD^h9njAPzdUByy54X?&_Jm)>xZ=A>js$s_6YmY~s zd6r7;KJ+arXsrZR`lTeW(vQ9WTj|%Z)e~<wwvcT+gzX!Z-<nP6f6s<qo|?JWx_eO~ z5xAGH>+*6nUeoO*r&AhFafn25Mni>YvsvY$y_T#_VI&SLf0lnwry~OAXeLUH1#aby z8aH_99Y>Gt)u-TT07l(LG<VZUcr%r&>xshSy2qUEsGXzflil?Yy9;bhj&MA@b`72c zoJe@LpY`{VzO%g<zZ4nVc;UGDSGe6pybUYXfj&Vxg$cK@vS*+fN)%k^?P%ug)_TO~ zLrx1THh-PHPM#dmN+>7sA^NZ!##pB=uorG>6$I?rBUC5ijpzD|fmwa5?MF)Y|8_)! zBA*^ZObkXg=4xm)$q2k4kr^_54J_h=nS3zHqdy06wncrz%NGcdcYog>D)Go-oq_qb zRP<=YpTTm=hhA<}41-jHewpaYS#hc2sMxV=e`hR*lsIUh&PgC9wG-<**(uvdDkY1k ziEr-^P1~v%Ta9u2sW<x`)H#5K_ek~r*Ue5vmSLLn=ZD5moc|<#u=NK17yb5P3_KBQ zwNQPnoB@MMYae*sY49@EytZa6UmA0+xto^1aGock=*YKS+f%pz2rHoYBRw^<=Jf<A zaKQ+ST-8I7nKk4}o01}PROb1GqO}#Xjpv!hPyj*2kKK~J_ry<9XxE&R;uzKbTHi@Y z`GbskX<<3R@d45FM^(*Q;{xBtlzVH=0k=$YX0jGv<_foq=gR&cRc{qmSJQ+G;)Gx! zxDyC&!QB!dcnI$9?(P~k7Tn$4-Q9w_ySqD_wZH$&JTn({u|rm`UftEzRd1O|jd9L6 zrP%wVmB?j^VL%Xi$KrPr3a{;)Nt~>@KkKB7UNPu+3NOI*FJmHnsZQKYHe|HVWCg_E z%N%^#tf$zgS5T~%R;vlc8-AnY6dcFYw7RxAKw*VE;7T1C{VX`1G!p(xRNg1u+_q`7 zZAAK`2>^vcfT2(m01Ab4;Jk<+Nw7zc8^`HMgh&rxU)=HVN*^vwNq?>_9(>&xBy&_> z9YAQa)sW6FDc&lDFs#r1H_B|(Gqhr%%O2BoscRaxsAgbg&uAaRg0HX#wiGRdlhlcU zCa@fwQdL4g8ZCw3{fBE9CvS&bi;Ut2UnyK3H90mPI%SM5)qXopv{ZUJV^U6j;qWSr z&<|E;fImI{fBtmU2r9p0eV4E2xsxH%R$)<6zSDmSs#@NPmTwi845&|Qlk2Ib2(Azt zvLVNq#d>fzEPPp6I)TD(NK<M^Huly&F+cETAON@x#Z(wtBcp43f?`P8;d=^E(6bA> zXUpU-89jnKwx%t<1t8{ZW#7^X-c3+r_RI)7Bdw-oG-)*)RaD<&PR}{Tytd_a=Ml;5 zD_F#_<VVpFIN*;70cj8>0k%>;6N3d+k?0N!G474ZuYr!5<|25-XH`A?)D1u&*XN_b zU?3B!_8*W5MM<B+Vv74;CX|R8`~4I%kO`Fs4)nO_kBtxXKWlxQlj_@RR{Y^W{D?op zxVvH+ZmD5ta4C@1S}x!|EI68LuXvoK`PX^Ojoo;Tg<msVNGtT#;;hWrZqQ$C9FQ+x zB2D>nLU^Nne#l|q9xwPOLkw~}kQgUiw9(0n`2%rlgHt4coyGgVDyN?gise{ae9lra zEXQ(9JfiWSo3W^uRwO5P1mWYd0p^D$ln05%sD^m<0GQ3mf0)hBenv>3jA+K7P~4kn zEl|dC@CCA4eKrLPdDT#K<204Sa;(+V!wk>A0n2)6#eI6`0`3-U?)TtN1OKO4O)WLH zP=~!{6Wzzk<Zh}W1~)5VYnH9eS?zDEB_=j9-_vB}{0R`amy*^<UGvf5OG+`rD8o0N z0?8B>O}q?n_^(I;`+YMjSE7)kDX?@p{WE!!21Gp#QKledYe_f`*v^zTBPn+C+q24F z)tuu;J?R8kS@6QmuDMT30B^Zy7Y@&rc0YG>EiU;D%{^$a1>Of@EiQGB%;ma(;nZ^g zPIUp`RIXO-b_hu}$;nNto(T${yTKF4YwO`R1vs`t4CkG7pBbguvPBWgeFt;Ie089T zD^6YA!5o%W14RLQp%oZQSW0YW!)wBxr1&FlL2|ricCU&*_BSHtz67B<+iHuqVC4B% z1=NSLt!$L!>N5XUDM9QQMujv}C&)!E<0U_?>4&eZa0c{awSmt0L%v8yQULG4Gv~7a z_Pa=Jkr6d>-6_rz#zcfez)N@PED$rv#I^+2a>q`I3f5)njj-3!YRc#nEyKqZ^<C!m z)YV;<lL^i<>%rk;t@=xIChY!9vatmRe;N#dN;SN55*%HvRRdFvhRT-$z1}EO->0!% z6|GY=bDhVQ>vh)H`Srr1v_cy!H`^@jru^NO0d5nIJhaTYe4)TV`J-U5u*}*`eX*8a zaeGqL^VY(?JQ;}MIKi7gdpENav$I(5?n>CB6;v<F*0C7qz5mY|(a3AtejewoE^!@e z?S*1Mpzsk4qP!>WaU{=uvLT47JXrN<gH9K;uk7N(kxk%Y1!4`Xopjf@?w9?V)*n3B zf?Vm(8Umd=hL})~ynxji;LU#y-#aro;xi^^uMD?%!k93>bAJrh>YZ+#k`yvu#wuQ7 z2SZZzmR+@${}+;4?lxZrLsInsj{=a?vZ((9=>!1(aHswT!#`xa)XNM4FLfzZH2xa5 zdjT<)o2fCDU8xoxU-8Iluqc<ic@}pJ;2gnWoiVucLs;SRDQz(1%z?-j_b`7#jSc=+ zeB-;{GII_kn%DifHssVi!sSvo*X5F5$o#_si>NmJR$U-=#n2%ljTZ0~1&rk`^mzLe z=)n~1AwYekj|!sGg=tb_ca@azv<Gh9c3EC(iu4y8J9+4LwR!e;wd6GgmzyS_NlAL( zcSC_VE;3*c<NyW%F8v~>eHc+54*vh=LUcb-ALkax6`7(vZC)I%sm-V_ID-^Z;rSx1 z&P^$;&TNX85#6s-CX!r#GsVhPDwGF4WR?AK`HqY3XqFYb<lITpmTxfMEL2q8rkE5D zr#G!fuL)*_H1{l$JP0k8Kgt?5J9MYkJB+2)8|<<2qlEuf4dt>t95uD0@lOU<87T@g zmkEUv6Z+Xp$)5XPvdQ>myicC$WGyCN`DLsSBb+*SmY_O!fuK4wEFSUSe$Fxx-};-6 ztXu^`nBW={sr`zXj+Z<^7TJTnMm0ldQa%ItHN1QCqXk`_N)YR`?uP+67Kf=h7DEJv z|ATCQE>7_bgCW~kV4HGq=6}zTpNsq*n*N;99mKxo31MA%1{)1Gq_1)6Gya8V6JDKr zA6}hV04Kw>f1;!hwI1kE!R00d^r$d9I_SIgx9nd*+Wtxzl?BveEbvjU&6^ir<j=$8 z!<ig~oGc^0Q>cUcqfObWDY*~O-szV8IF47HOH2IPGWh^wVFNhgv7>RVd^8bXu{Ap7 zfRmjs3}FLTIZqs`1AAy&H^WiUH!WF`l2=P|AE3QUps+bkN}Wq<{Mt6TF-ze9FhJU9 zBT>bu3)5~vRtU0Djea6j+-12kLnwup*pNVravj29#?f@i3`qwc&*rrn_x`mSa|SkX zc>jm8zWurzenze=Ax|yfa{~HrT`l1jsDHvzahFZ@v%uP=w8=d>WCQiwVnu{1XuLU3 zLV?6S`(~8$ESlqu_h;{=0+7u1>1KtrXhZ-+^a=vU!W0+BLUdEt!v{7|J9?|iJZJwQ zag-xCfR`z`%JEAP_kZu;6BZsAG#Givlf2F+A~qeb>Sh?&rq2d@u4bH3J-s2_kUR|| zWGxJHWi5aZ`u<Cpio*haBli9O6Q;N-y*<%9^sms<wpDtPpz6I%F_t?Ro~QAJUScVP zn#iP;V(Jyweb`nbRg=wk8qg!E;_M@;Fzv%GyG+B@)d=)y&v9a|@n@L8YKpbSfC~07 zwr>GO?g7b9;sl_WyT&-u`p!`aP+GYpfC$%>rsmB<%zNOhpy!QAa;epw#45#3ea&cR z{ZaS*?|1HDrQ(R{zKJ%ofFqlTYHtGea_<>w$33d;?HQceI?iDwJVHaYp{q#jp8JP} zm+MUKla|rboJ4v#?pOS?_s#}Gc?yBAkH?gJho3fm{ebok-dy?3jIs%UqtWd?@Gv?D zg79q!53y||ZTNaX?FYQk=mNz=Anx5#tJD(H%3y5>B~s<JT;15>tZSF*zQti{qGLp) zoPJsY*73@)`u>hEeBBtp@aTlo+&_C<U~b`HsM!1dGo^d*YZ4&dst+jgho<~)RE)Ld zk%$C;v&}6A+H&fr(c>8RbN{cnOO@4bxT9T|Ki)BLa+xAbOLK-i(A<vz@4W-yy$9d5 z$+G<BMjgc^#g`{~ys3EOSp6()M8xI&I29t{4OeEoT-!pkE&!*64Jd3++IT_3oxUGg zO+KtpdImIN_-V1&)$(}E8}YdL6KUEgW1cU<I&)s8)7uzt@$oB$cL7z>tatm|?Hc_- z_rMG`BdFJMTs_dd^jR=~s;WPLdj3Cn1J==se#GPFHWp_CZSQ-ipDeOC<!>%8O~ba- zFg$514r1g96ea;RP{vkDggAWX_aHt_&H~8*5fBNOu})I;GF?N?Nb3oCSXO*q6S`*e z#IJrZdgJL~ftS;O<+(n5{Je7mH+v7P<TSqg`p+LOocmgcG6q^?VLguD8N;=C9!j#s zOHOsY6Z&wu@@(hfhHS?^+O-)wJw)+Vl}yn;8^6dJuAWI9I}2$TsO!1A45x3EpSw|d zK|gAaV?Ul0UzLwGW0jqyx0BvO2UtKTlzES2p&R5v`qaEFPC<M-CeCQ>$Ah4s>xgT3 z8^VW>J<b3a1lq^+RUM@n=3kJ8=qaPdiZr^3_V*ge`;MbKHd|q$@q^wWeJRFRsjZ{> zGCi?^3pGVP6Gi8$AbC`^Db3O(x4)5&@~eGnp>8&(v;I?OP*#yKXWuoXn_B)_q*k4g zwzh_O3R>v(tlv=OcN*1{oF#o?c5f3oe)C+OlXjBCXLiF5zSF;t3|q=*Sv#pc1iAvy zU(kEc74v^@$wb*FtA#!K_j9nIh^f&060N)~QWnKgmN*NrfM%FH1Aqp*Te8M$+0M`c zahty~!~c|W|FBgzy6Hq>ktedQEA7#2RjMOcnx*A5=F(sp`9eI^?*>aB-%}?#viYkW z$fDQ>HUr3_xZ}l}z}9`-lkKzmaDulDQz*~?dRlHg9{o7gokstB!p~1%6uY;|BJvW; zWUF^yVWkC$52<*Vt{LuQKh?$-b9RYfQHnM}x~lGenBIy85qAcR%K?=*#h1Kj9hfUO zrgslFhw2V>+TC65^u_WSnja{&fWW688|^Ou+Z5@*;k!zJEuOl|)pYw+Cnb~Swx&ed zM&_u&NKSx1BfJhlE!WGO@iwLbAyW<<<iidr57Z!d8f_(rsaQDmt?Nk>RIjWprgza= zcw}%1vmSDYVn5dem#7mtYr0?%BigPn&p7X}Nwu;gEgGfilo3)yHc>9vNX4@mo$>Jy z#y`fWErgucLZ&*$5S;Y^4p|yhKHlH{d-ci3xxqdwA}<Md9jMpdVbBQj7B5+hSOI<| zr7YXWrCD@NV+;+J91`OGexRY4XjdJ_$6<FU*&G11bNl6Eijj-v5%mXJ4?X1==->p) zzU<P}rKzR>WZPB`K1DXunMBT7iljnBtIv{}07)b@AIg(N{{ELlqKBs89-0Xxkzfe{ zdZwMe8jPu+w!vD;56rY<rEpuQ8SQPN%?jsW&uB+JRnG-ydmQKK*aAj$SFiUV>?CN< zdA^&xz+&$2ElUvl4gu!*WINaMD)^ZqKO673zq{f?6q8{n%UeT#a|~duVkG^{Z&>vV zH3HA)E2^vCm-U=Y=Rnx*+${psOA~1{V))MQ6h5gP8ftT@8DRJ1-~0hUY7Zk%NLQ$c zANF$4@egsxlAsDKdBeOdD$c44YN2xP`Vj`!`?&_zgSg%v8d*h7!?A$hJa4%(K|q?< z5?~t4!~>>**$=|GJwJ@J&4X>bjDs(Ce6HS3sZ`t{xs;O5ib@6V-tN{l1Y8ljkL@2V zvGk{n&~&e?ooPeOu`uIqC({(u(vm!}oqzt04Itn?970tU?hvZjhaGl{TMJdyIHqj0 zy4Fg7*o+Q^96X%p;`;PQL{U@>?Sr4sTgNuZ!;f3k?_D+v3BjIp^<woMWshW7zq2j3 zTbsgTnoT?C+Z8X{V#FJyuT&-+4K*-NLHRq!Ec2_>zkM+Mu)90ModLptyUW2X`(8<k zv;w1>+5Q~@6RudDrQ(xGps#$4ye+j~QsdUe1%G&^-xuC!mqD1u(W(5Ch|s~UJ5<5) zUoKfBD=q~jD`K#^0(X<esI(uurDZ=Im7vPPW;L^2ANeZ@akgDhY3Io1l@qKSZGR|? zq$6s#xG=cAdl94-JagA2SaC5XSP|RS5wMwTwC>|<MCcddyB7-gKbrg3^vj$e6=mWI zs+WfCQS*5B!$M>!al6Y<fo`>x<T4seZ#y<zSGY4ktRL>Kv5=pYko;0~KF#P8&yW;a zjS@W9!gU=&Y@Z!}j!S+33=r>yaf0<s8+A|``RRmV@T+DGtmgXtcbLXY8JNZ>(oV0b zY{+%c2OD%@AGZyE5X5X*5_E<V3}?;7%*(O@c~HA~8Uf@(!QeTJ7N;93oz;FsC7(97 zv>mFuT4z7vI=~$3>!MF@Pf4LcUbO8U(ig9Xlc-0}@(o52UTW*wDU%xt3cECHFKNen zI2V!6#hZEC_YX2do9B~`Kv!=4k18-OSt!7IqO6EH>IisFf*@#*I;Cadk3u129W#OT zB=u1uT%cg%B)mP8(E8Z8xX6S7laa>ofe0j(di52{;cS6T5;78I$we0xSXR^(F?SsS zzeyTM+PhBaO86sMNLhFAFcLm<L<^#<oCKIv5?WB4b0cV#>xZjvl$aVkAV(gj*_MO% zmx{f3;^UcbT*b?cM#1ZiX<OxCC3n_#A|h(DrEMmIe9&5na2#t%a2!*t3y~(Z5?vru z8+0lECke<IR$%R0<ucij$2y%yp##1-YF^f#hx||+oRP!adT@sysG;fvg*?WgLJ3bI zR)7%Jaw3Z{e$r6%62dZ4>~IF^eg7>5v36OXI8ke)CUwAoQkGa*`Q<$^Z-&hmUDN)< zX+0JWmQ{}C?Vmz8z5*MsUH@U(n{JjlXPO@C?^xlt><U44sZhsprNIigW@;?Pd}YIx z$YwSqW<7CuZ&r7XFteRgr)HUE(afIM<o6=(Kem$@x4w#aEqf7P5e5MBaub?F9H;Rs zD*a$`<F^%|R`|hBOI90A@CcTzEJ+!r)~yRDQ%n7**D0qAdv@mx6W29-I!qE`-6Y-I zzW<)lE3R=-#e3p>Dk8{_eIX=B<+iD6_$?+5GTcXY<u_uS(5FvFw9sOZS@~?&!#+xa z3qfq0)Y?cYN5h?~ZxNZyDlKKS3qu9`r&RIMiUSA&4aw(kRMn+L556-#yS}K|-+|c; z`!~^{ng|C1v!g=wE0onH5(Iq&W|sh8o88S`)n)6-Z&+jwGr+?a^u0^Bs%xlg{&yYE z3HqE@9`{LBq*A)}V<tOB+eN9c%HU^TQA?_`!obg83XyNgp>mgGr;}Yn1WVa9pgT^z zDYS<|p_N;B{Hk@fAD+5wx5Z{-olWQBU3~g{P^*6ca{>A0(ZQJ*1cT=S=XWBjjU+gX z0?Y*)y0^L~JTP)>J(tE2VMB#-0OF0Kny3@CjGulB;_=*nAG~dE6<^;?dBG2FcwZ14 zJ)$0R&D0u=dFzdD_HPwzdo@NUK+4?BzMJWsJ=p$ysbCV*oqQRFugf|JG``T5e3&jo z?jx*&c+X^?=^>W0EHoV@c&jz*X$rjdj!>2O=f=^@4psY0Q`Fu02+-q3*<xE2ws|-o z;3l3Mi;DAMMA}g!zzh1fBWkd~76Zg%2Km_ayYMG}ZVs>vTvw)qftFg(_Lm<0atTl} z&aN=ze$(mOR&?8h#ANY`le!ss;+j}ME5*oj!3KDl)=#K1t>~yS<pzO*Ux`vx&`@Rk zTx8Y1bgtTJeJNCDDgVTZ9jx@l2j6{Ml+Mw2!!GitEHS4QiPs-h#kzN^Ck8jMk?3VI zm$v&xNa2Y32Xi(_`%b?uMEA{J9`{Y*CB$d(aHVf5RoF6qZL-?me3lRa8HJ5hd)M6r zn|7X;B&jLXTBQEXFS^_D+E!zYeR=zD`WeM8%`vN$*<jiqbjH|V^mBlUcu)2@)Npl0 zTsaYje|X|T7V|1J7-@-4(4g+K_jW?t#Wy?Hv@erUVG}5m381W>@iR2oMzgc|$Kgx2 z+7QdM+7rv<K38UpnvnZp-fw<wL<Jb9@L6^h%s9vVsp^$5$JKGobT-u`WK^&O&R?vj z^Ir=}oLAAXc_>m9)_JX?wK-2>=!^_K;9b1Bdl8YhdU=qygv~xZqlGI`t4xx}_`&_w zruH%W1bm?K$6CDJCRp=I-5WXV&(@bRVVPqpEgHsyS#;*{7uU6s_*$XNv}ZE$apjAQ zBJw@y<E)q6S)7++S)5TQeO{G0v)D=mpqLHWnR)C?6cI=By<6C_A64PjI>lN9O*4*t z>dnw(g_P{pndv4=6^f@2`K~;zee7kO#QH9q8mTV(8mVIYdIEKmAb8rbK5-j{qfmI+ z{cJ#zZu!nNEg01iI+$I>HOtx5x4b35A{LBFu5x1SB9AP)FT{F?90C=B&3l}-R+oxj zQ+T_!tQ>Myb+xNk`QE)RpUY`1%!T7CS^eoKT0e0Dyu&Gz+gX*dWzrXb>AU^)1Ho+{ zOkwA5U|pGnXl*onLL)44nQalamZkB8Hn(b(X~LJz>-`C@nY`TYZY>TX&tLWm^feo_ z`A%G?rN^S3v@rfwgt~o6OLd`4OBFjb$iOgx4aGd%`6@<v3yWCjAP0=x^_@0N1BkdH znNDn$z1-P*(9Wbw(^3`BsXy3pG)fk^)Ert$`PlTc2XS_{muGfYxR~J?AyUai<+rAc z-;tcQiBB;Dm<{o2i%0TcVQ(6WBs9J?&JCgYtY5~3Sf;Mj@RqFsMGi?af~DFflx5nc zm1XkUtBD&_W-`<!m1X?f<s>~DdB%z=tVR=`l0$#wv<Lp0XcNm5ZI|RDfILr|Qx-th ztfft69?r!)k<{!*(5`U>ihIXmxIT}wZXtTOQktjmpV-HXBDm||>V$6uR@oh;RoT&m z$GzH!mY|j5&=z#56jh)JhQ|R~g*l9x{_bau2c*wE;H~eVB`XvmP&T9uZb|b-wtl#T zU}G9wPtAf-32@nLNp;(ANtIYP6nLEkeWGn2F}=_{3jHLzo(H~-1LmBYYB$2Xq~GL} ziLLLP!ZJ^6Gz!}2mvr?_skKn8hFPs3LJ=7MCCZ-zWZHjHz^TTh+ZV=#Y$WB$1Zma} zK66qN$xI1YbW`j3G>##Fko*bZRnWIEt_AH?db?zHwz#K+&*qDb@~_AlAP1eh9-K0Y z%B{{+IQaZ_3J6CXu(pO^LYOFN6L=HoE2+uQn)Vi%8NJ|P*BH&YdQGDpv{}&&p@gKR zLSL|^N?$N#wiszv3+e*R+ToSPKY<`;%!+{fS}(Ist^NzLl$3O(oHE?mtvwQE=XqK= zgjHz%Kh@P(Q1sbl654!nO>k|1M_D(b@$L+d@vZ_l?Q>F?5}t~!l8k?ioHm{hH!Z*k z32?PzKof+nC|A|3=y@dTeYmi!#D^E7&=4!u-E@qs{R6zu>e?lR5+lKPa$jhX7=`2I z&jF&YKKaR`7N{#?z=cdI=@|$~Qw?4r-3bugve-}9lTI88MxM0?&C3ofRW2((vkv#w z_~;XEcL#UAbQt`7*C*K8xr=TguG=vot~iuoufd#IJSBqI>5a^pdAv*%3E<$@!Ik;j z8FnSJk{E@UBka5L`Kfg|x?+q>GoPV2tP<Thu6RI}BDYS60$eI8lgD07JZw_ui)p#T zYC?8f2U*AtJ}{PoCE*&26SSWAB{OwC=4}L!&&F2PzO4)kRRJ+6s!Nb3X04P$@EUC2 zuD?@qUCU5%#c&UKA?YhZv(R!6sZNNSl|TagUIOW8-aqTJw>44hi~FL~B_t%NN!+-8 z&ZL0EV4W0e-E;DQQ&kEo<}`NvH*AgVx1_y(ks~az0bOCFwLoFKwn$+-#lARhRr|Rc zWNC+G5%csJYK9%WnE{z~()H9RVRF+u<Q3FwYvDLTQB2P6s$26xyW;=qG{~Vn$n5t5 zzuN7keYK+?k9wUDEg>uwBQ+NKTvS0QNFD_~Ch%(F2KZy0gD0>wj^Z0K@BM;q2?^-w z=aQ%Fnf;Rtv=8(~cJOAgD+8mamLBk`2rjxVsjkK@sp38Q|5G}H22{WCf|bsmoc}4E z7YjigoYagTHLfH!M{m4g;sUn69}E~~?4(=vW$pRw`e9!UBsl{VD{yCzSEf-(>YWPv znA5HS5hnn!IU}~oZX_*Y`Hr=GTr-7DX$%>}`!d>h0hHVa|K*;ypb*L*K5;(Sa=;h> z#d6<~;go%@By~)MEy`*C3=zi-J}NLKc@eZNcm|sP<PzY@@7-YPx`%hwvX)WpGUis5 zqI%w%fl@`58-cU7dug+_6lD>wvZ5u(r7DERT}Oo#$bw}N;I)BPE78ZF?2J|Dr*Wj; zP-A>Ge%~9`z1tTdQfzU8P))5m0-7_oW9RoJIRnH>f47Yl{-h)oTBD;jBTqb23uwrx zPqq1d?5Vo03#bZuYdkXo>*5_x-;yTU_-%T~(dpdp7V^!8TUdEcz6gw@a3d@KI4Au$ z+h&h9mOEWZTwtQmu*I4RTacB0lqvHAU~Gi~2Rsl-`yV&PAWG`4p(kcIHTgCu65<Sl zF!%gE$;3$jW+lEeOck=)<8VZ3?lU_l@Mk&1ISKxXS5n3aXWYe9Xv|iq58U8fCDere zq(O`u%b}CvGG>11VZPIi`}nUjCrd(N$9Q($D=RY>`xv47Yq)QFQy257LUdT;WjJqp zT^9w0Oe=)@bV?0I|MW)tHme?95O^pr#m3Ynm3iRu-yl<pOq;?-c8DC<+^lHY>_dy! z#LxHS+~y-$Sc}!7)<*qrWPT~i9;P+3*%!-6oC`0YY|h9FM%&LW-A4G9u4hHxR$OXo zRJDSZh#!;N1o)eR14@es$LI&$jDu4oc{Bu8CrHfuP&iPXIQa~OPHlj#`XXr>jNasq zrPf^fb)UXJFA}oNTUg{gqhsk&*8E7vKD1$tYhiDyYYvs0bxqB-a@5~ndR$!=J_VS= znjH6n@IrdvT#e7y;L^HdLP%APM*Ng_h5kor_2a>8_`hZO-OLROM`@K741tEPvOn}o zFm)GsKaAX1z+%Q1sk0c<=2c4do~kO{l13+&vsbtvdGxJ<UW!;6c*{vs&+va;Z+}wt z;Bv7lMzjy@Sv&0Blf#}v6=X$Gp9~!JXOJ4#mDL1XhxRD)N8Q478F|UNvC-E{zUhzS zw}$~TueMi23zpPtP}`sHd)~?yugEsj;U+$BO6O@?p{?;8&TB|?JWYZ`U1+B&T>p9< zBB0W|$~73txJVo(d^o5f@Ng{rT(efiAK~*PPg(fhx#bk(VgGdef9+9ks4T2+ioQ*X zh%N<CDUz5f0s)g|khC<FuCl&|=8&?Oz{IZAeB@v$^p;~|D#F!{K5o+Ac)&RJ{0nK; zRjw?ixjNE}jRj1YAzj9PXLSQr`Ad17Pc(3htorun02SdL|726^(-C1|M@?q(?1L!A z1x#fvrU|qDrB3$UeAhv_`Hxojt}u$-E8#R>j2>G|oF5{a31_x(tHh($nICzM$^EdM zYMPt@s!h4A!7UT6)Z<8jZ%JwwhNw#c)}Im0<kZMg4S#!zPPgb7I+%yoZ_DT6+f__d zYkXY#D~--(pt9v=nzH4WHnZ1<zG5gWrB<`h4aI3CNE_!=Wyqtsuo8&TzAQyZY@#tw zp;)=?wZQanpW^`I@REQl%5mo<Z&sO4g6Dxpz$izSUlgrh14N+PFlU!Z3)gNk;T79} zNx<`!9a*|<{$8#aQZl>ZS)4_S1$u(ZZRE`hM%=l1t?+zD*a-Y%wVC$E>WhfO>xl?0 zWa*y+EFs|nMo2*s2Y`tsA=J<`Q^t*(9+sr?L_6d)Z-^gRy3u=o4cC8Vs7PTRGe??p z|I(%ILfNG*hM*;2FsWiK;@9xuGcVt<5V;F5g6c2Yt`tp|<~tEx2_)B_PkREZ`wU`G z&C>^<tDxP}GPB{M)k|~fF|+pHF&Q~MPWLbGEoI4D;gO|{`UsLNVbPt4&g<Pg&g<Ws zpPy92ls>E2-qZWF{L=pH)BHRGT=_7yXYEag%Xf316M?GHBOC@WeZlzUewG^7zjyrm zGhhktRCzQ`i7kI}ACC3cX=}M;ebsO&_^KgBt}8G<IgCgv(JdnT`Dg@DmK=;6UHK`A zb8>hqu5;=aA$}-m;r096g-qWyq|!f%OzTp2-?=0pOELqeHHd`0HiU#NXS9H7Si95- z`e%b?5cISJp<o0CA=oqHaK}cqM>}{mibA0{o`G=4N}Ekn5%rO}bwk|Y@*j*lfr#e- zQ8S(dGvGa!4I*L-X%!j^6t;o|;j`&(Afo-yc@q^mMH@OFKID8s<Ifk<pcC*l`g|OQ zPrN4561(dt?=<d2G$m(+vzo_NvaGEn?TUXrn)j}+e$f-P47aDOJb4E=%ibX?Aua$s z@&dpk|1xxYU1eiJC}{+QZjjDkLu4|12ZT5*L|JI%sI|P5pCN-|-bKDu7mmco6?tTB zwt8nn%4#n>b*|i($kXb4zf!7JS!{+@d2EK<=1SsS6U;zN$4$~g#9K_*!e+1;CL&bA zV6nZ-AT$1x!Byxo=&bX@5dsH0DNu!0@jTn|(mZC)kt5ge$>w34d)K-SWM{J+J8)X4 z=zD3`JB5h4HN}IvCFl0(Sv5jYj;_q*Q$m-2AwBiR87Cw4Oc`zh2B4<XQvb_Xg3f1f zRJ`oB>i+N%5j#TRK+7`3*@H@j$12z}p2AVp0`-}<T~acPv>R~%7;|7tjvMRwFkDGk z#g>l2U;nqZun#vD*myvzjpz$JM1bx`kktIfxw46d0gq3~E@}bTjo37T3+}q|8g9n& z8WJgb0)~@1)_wVnNbDkfCqkAWfV*{nnaf*D&xd(NhKM6u_o9SB_M=!(|20&kY~(%h zwp=)Ce3=q%=$o&F$*sjE>dkWo=!WEO8!dcFiTBUQhR}>W-i$x^Ql6?M>Sd1~C2{7L z7`i+a9Dmt=nhJ0D$a3dn7u_|57`Hja6SpZ>jPz^^5_P1d=`obWJt{&eW(M?B8AmnX z&o84b3I4H(hbTAZFv8A1=pG~|V_jD~(C}%tHPt#CqFG5c+WsOiw8bDW<VjU#l$!W7 zkfLkAC1ny_WHJi@Jm6Chhrlp8>V^#<h35cL_~GKbTkF*dnPPP0CZf(;sJz1>1w?Kq zRR!ZG$q3LnK-8a3;F;9;bVNuRk)VV;`5<UMffZ-eiH|S|`8?Cw__Z^ze$TH&LVX(( z6ff0A;&W-Ls;8i8QL0a^l}HS2HAoD3hLsurOkn*mM>d#M;BH}|3Jt+*<SS^7tI=*p z`81)CR}P;Fo=%4i#JlN@<kGBV2hb4IcI<Krv1wz9r)fiO81~sWwBM7~uFLQ*;!zVS z4K^@~F;BH79sDsPK?1zOnNH^d@3$DQ-EOUpa=Dhsil*!k#$}SsHN@lfDW2nXIkopE z+|VF5+PMzH%D2PcsAjKV@YGH1c>Tz6;1)$pL2s^QrVFieEnFYAnp5IzJHI%YIgspW z81ZmznCEa!LgN1EH%R0r4)bu0M(n;;IJjjy^#G9KA>~b+tl25%m%sF5@Rk?aaG+BN zU6Rr>22`*#5>_nBN)><oCIVGl5`JxL`?OltioRNwdr+Q{XtGgH+N=&cnMSmdnau-i z&qhAxYT3WrYk!uw)FTE*TbMy-U3D+K)8VarT}RB*vu!(kN>lk7ZTv8fb7d`+bEP%a zbEPRj#nE8BQm<Mo^ShP#<t2LzR&lPT)y|dnKo62Ram^4mRRv^)&;%<M9egL&MC&P3 z!%yuCmlY(luv#v6N^7onN>erSs0OvoogiatOkVe=W{8=Z0zk$wWZs^X`LO5X3)4HM zU0}KK`!#okYd)QltgtT+#AU>&RleRj;&I&SM#{3d=w7P38egi5hiVIyPMX2dVs$81 zJ{+3E$%bYCLO0oWZmWTLefcdpF|Qn3<NWr6SzsP|UAJXCyWuUvlA*0u!71a}aN_O@ zw&|Gn;v4DMVthitX@(5wl%aj3rOJAsmWBHSlppr+_-{bKw4?swJ4xe}3`t`&RF_v_ z&McM^j9&@Kr<r-IOeip`*TI$vR))LC59{?aJJN40YvKu=R@-Gd9a@`2F=k>IDy^i& z7R;X{WD8y4RkU8<l($~vl;;jqjzia0_ksp&FnNQXs$pjI!J?in>lUZ}&=O9gvNx}s zb8P|{M~JB^-xa=E4#~0#J;eZm7K*VqkoFuPYKD`aOll%JA{vb!x4wIdK+rINPYc=v z2yyuX5mGO<UJTyxe=K6U=3DB9$T#Gvp@r?*hgh1spv9p$y5wC01yhHe-xcQ!5G(Q> z`e)=BB`N<B9YBcS{Re*&%+%(0u}31K6^zT;naMkuB7Jxd16}-DO&SX^8<a^9T(YhN zENS6Jz=075I56S>2SyD#@+RQGC;=Q8WCZ_nU~Klnb=~gQw6?JR7($a-%tIsb*zVkR zdQ%}AB_?*m5dW-PI`}KyrZdXpG?2^YG>yxKe$@B%Lm%nm5{S!&@Sm?*bkC}-j2&QS zex27Kz#HP|$Dbb)p=O)VJV<)i(B{c}N_ZyGSIYlA8#}4{PU20gq*!m8w9=r6OS0Vd z7qPr82C+O(qmsDT#G-)|TLZo+GyOEv4!n031#yVw$ZQYL+p&q~V>ebXVHr~_?XTqT z3?mOIXvO|`W{=<UVg!}N0)4Q{yXu?)asXBer|eVZ7M=$Dd}jM62uEbFRR?3D>2K+U zpY@s9zNjRS!x2$bR*pmf#pQAmSt4&E&=dGBPaFDE|JOgFB^ad(@bg{#g%ucrr(h2e zEv(w}hFfQLlWwdWHUz`XKkssDiblrn*Y(pjnVg>W9DJo&+jIwWZgGtPRonU8|Hle~ zj9x*c+*snF+)(3&d`1I_+R@5%I@&-T0l_LTU|DXbMyZ3}YwXIw$s4jLJ(IvRfp<UK zXzy<@U{Z7Uy|&>_rnVtIyUj~38*&+BVofOQ?xy1px&t&J=oF>ed85mcFZ+Kpg35Ww zI`4vxemGSbB8)(+GMh76*qfgNWSVjuOp2FMwy-zX#a;je@>t3M3P*~mrM=~b76c1! z^bAD3`>1{<U0?9_UHVU3dF<u8u|EY!mx!q3XQ9>H?XeeUi`yaDt{%K-yTs9`m+*o{ z?wkJV?)(1glEfMUbd!meeeew^{G5C>LY`nlC`tO*-+bGh<h@jc!4>u*=*;K*ws)^* z=cDt;^0;BDa!Yn;&T=hXC_IIhtInxQg0w4PO)Bcqk4v`pZTL?R@$mUr&7%|QA#y~W zst0eprP|*0nnYrVngR)v!_c%?&2=J(X^O(Hj~46#ZxEqkKo4Bx#%wch4Q&a<NobeU zg;q87Yod}5(2lQQy}X-B)7#IrhjNTWCar=hLVotUw8-+8cgb98#Sn!wl(<4C>ycWx z(h#XSFRNK$8#MuCGt#2l<AJYg(gq^O4Jc{?d}Ts)Uch&mN*CRtU-A%G9Swf%XE8&4 z*}p_mJ+o(K)y(_KFR3tkd+F??M8}?A%wKTO>MMWC^?g5F;r3P^Ep!Z@wzyqd_SW<e z$$5Y)ZMF<|TGifG#UUN})0zfGW^XCQ$@quC@X`}5+`{QEXusl*9uWO=>xw+a>D79M z?lrRNV$W~&KZnmL?YO6qtWQtlDYOF;Idv}fiVv+bVM3ZPc_yI$wC6)%SgY;3fz5Ds zk?InPl<UGeThc>b-mmF=8Qz8mr2Ss|!11Y&c~ntWes$X|OF#5q<HoX->A(%aFVB4` zjG^6KZ+|H+ZC1P%=DXNf0V#&?y9|e3vXz}}x~b2clO>I|HQuP%OE&v*36x}o{?EZp z%Rl)LV8!d?DJe?52f9r-QA(~71UF_lneBA6=WE>z!n+nAC(@vUebpS@u$lhqsnXiJ zf$t=S6MG#}xUR@;j9#IqXkSC9PGS6(<umfE(u{pdX8Lpvo&q26g#@#)M_f_8#*iGm zd_$(v)41=xd_R@?isuA&(mSR{AOAM8V~#fknsZ*J(l*9#HF?QoZ!Upctk8Sy-8B3` z`@qz;ljV3R?H+hH;fyGmN==)Y;S{#ZeLGl1PraQDF9R)Ak*Pyg6F~<ndTgnG#xRv6 z(KH3VPO4an2sa>*a`Tl4{bv`cN>gW}3%$^E@*)1RQ;E4TM5^uAM0jr(+e*gVs=NET zR^L&a{qRM)x5}lV!BiefZ)t{aA1i-z#lBoet$F|H@lTK`?OBtw&G;b`@=-ci*D#g1 zPZis!G2zKb-tp)^?fuXh_98d;%k;4`{lxP>KTmH{osZyM_g($v*j;^8eS_EMY{*5> zsipqp-Q6A#ocjeB2E{-Pb<v|UMe3<G7mjJF?-_LiHcdOM12qBTQM$)$f>2lMPapeQ zNFTc#-P|}I>x&v%sHLBZRHU|S71-W?nF94;C73p)8<I15*fOlz-!POPob0bCYF8&) zws^V@ZFUe5lbvjV5J8aI`Dz-t#l2VnLi80XQiQQ}i|Kswr{X8lOO=)1Hbf(~A9<B7 za4sSmc;{-3hV@tbvMw4Cw<R;3_ywuING}t=4@pz)&Y@rwrn%c1nqb%?Ew$tJxkU08 zPklySVh_^;e5ti@6G5yXQ#F=g_N?EfBf6gkf15WCHE#(N8wczg8;nASrZCp?E8|P_ z5&WpNmSX~YbS;E`)FKxp5)}BDkY|6%(@UQFhbB#hd3<&Mxty9M24}QU^MZKDp5rgl zi|?W=?jBCKr+m;Qk*7i*30}A*RnHD5EFP^XQW4U0t&xoWcS9BIwtOo44Ho95+2^qh zgx#9`L>}F#m8ET?A72*qm~Zy7d=@yq;DA3JD%*D<=#x~0JVe#!-oM|!Nt^GHP+<5u zKDr|e$0mtO=$|k=MD9?p4n-H}L%&olOx*7RZ@>2Zf8YMl{(s+o_&oLB+t-!BZzp?K z33It6)A_cF8#T*N{%8hE;*R{P8BsOQ;f)P`@j+JAd{BpEguG5gblcxI2H?df<TuWa zQSJzB5lrHv8q3P3Jmv+&6v~a&$LCkgZDg?8E}tlk=0&aq9xJRRos?rw;+F>@H_c6U z)ue*g1Th|-jCQy9o(d)SSRa-u?S^bWUMO~BPYve*ZqD`qrE&)UaXkw$tLUl3Y<&Gr zmhufX4u03D<?ppw1tUzt&wGd9hlfR3g_kcJfQSDmk>X8FgCCv@NH1py4qu!bm$8$Q zcj)t0&==tHVlC+##g4B^jO(txJFyw#reChOBIAMdwKTc2Lt1E0Y6J5{^k?J+_J3Lc zUsT3*2C=#Wd;!m%wOAs#T@%|vClpd8y_kj8nKJD2+AU^lsE)^Bvy-04-I+>y-^&BP zup<$jG6>0DwFQv^1D_bb0^lq$>Z{32O>Ee~qGyt&Q~TS1hOW?t3t3})#`N4+hWcqK zALUy^=4l$V1u3D<Ypt+<Jtq9ZSI*9x2{=a|;5G?n&x4jSVEV8Z@hC2BS$!4;v24FW zXzU$+oo{u{A%GkfDbe%u<KOXosBaAqCutBCq_8@#I>OsEn2-x!KXl%Bz+r-i*=Nav zkP0|c&7}5^#_PSSa7XIxTNQTVCTSu+N(QyBWS2W<SA<}A-fp9WH!BHi$B;Zt$zv<r z<N-3jIX0UStrdHc))~}FHO~+AWc63zJU?wNFz>uKW%u`!p>n-8bU;PX$_V{~8|8hn zSd+5A?Zk$;$Qc`g9V4HI(5@D6mu*T?-+yj$C(=nS%{5{i27{H2#2x~xYw^MBX11+t z>m1s#&bDL4Di~tp#K;g+3kqkuSmA9k?$Ws{CB*(Fto5Y#$uLa`>HV2@jDV8Hnjh;P zLd73ii?|g-Fd^J_nYPH{KzUb?YlI5a%;BLl`#o#wFpVM=n|He2LA|{Gdf)hZb)L){ z4mu87CH(oVxS0^*cMRb3DV;+4bd*0=xj2Cz4&``Ws*7%ld|W_{H&dq4L(zqoAIx3I zCIu)kGt_0eH9}XWgC690?IWwV9dw=iCy(+y+EJl;eKz%V8qdeMCd1Kr<RsV9hK5#5 z>pZH9!o$N)x}p6ZwAIz(f8!35P-$oZ2|DH>p}J@>fi~pd6N7Ug?0K0shj@c}>COM5 zoMyCza|T>rZNHsYt0nvD>7dtKjgDy5k=op;VuP;ZUJX?`-t<zYrj+L*f3ixbU&cw( zJq?#eZ!sGC-;M(%iB3@&8svJVsrgabV)L@mFE}eby6TMtltf^Tf`C_Rq5W>&#PW}a z9fLaZxV1^m#G?6f!~dWSsav7Ui&)T1FRrR+|AUygEHR;Sd_Zch%)_>r8r6O9X;1-m zU&#}uAlN>SKiaL)OC@{ur_zm0OlI&jkPd|k)(sY8-5UEcf4x<NDd=VM%>H<H73kp~ z=Aj=+ble{xzjT`SiiuR?`5=yYy7b$uL#-dxHeKoWq%dmmE1zVt6xGM<QXNcx-ZOVI zvv~WDPx72Ksbmuw5;YnEmQ|Sob$anBa5bq|Pr#Y_p$(j=8{kaAzApZ)sfM(ROME$V zoc4aPHmRpfjW_<w$>BKG6ll-6pGwP_FxKQ1k-fPBvbVxZwRcPP=l<`GPMzYSWWJ}{ zSKty4Gk#7u<*`aQD2RV^xhjn8Xt3>hsJN;(?2NlEDiXKix@pgn#UA^*|Isl|yqwtC zWTs9pIt4B!HN82Lf(u~9nLO4Zdib=f$L|`^-Po5$1D!f63C98h4Z)tHfz7tJ4?fu- zW~bG+{WcF*sH-29MS5fO1U=My8eLTi|8DI+ArB~F*iUCBP6G{jn7}&7KrE<fH*=ad zn4FR;>5_*TPRu+1InyIX^yQa`VXHpJU*cHlS9_}{+%bXuk4bsn<-{Q-2K8GRDRA$p z>Hjj{z>_k_zZnlX;6tp%-ijs|5K}%E@E<~fONjoWRDgV7#3;X+s{Rm&KGx)?O`UqY zPlV@H_F+F?h_e8Q+_wSi>t57>_9G)>BVO^by*qu47ifami_LGPSZ3$H<Lz_>@f1bm z>pI4lg^D53EQ*8e5xZ>MIF&!q-P*jK{P<Cc$+yCKuzB+VZng)IK0m+Z5m|AG>GOZ( zDDn+k3uoHEUHrGkM_rhHlyCeLMD_mH<BtFY)Y8oE9#-b2hE+L)TJy@&&>uJxT80Kw zo2ElZ<Rd$<>R}?0uxyvu?;&d!O@`M&_VxqsIc-mX;BLMYi_BgVfaUE570u<XQ`Cj; z#+KRR*6O`5ve6bn4VGq;%XfPrNJpQ6QDh7x5*F+xT3SGfU;8U=OYYL@j#1Hff_sC# z&+F?#@0%8XAT--ADL~c}xwXj3A{IbA59q!={ah}L`&jAKpOrNSg0e9C4Qa%7_I_z$ z^hUnlv*5`qeoRGOzf56_5Jx(D1-qR5s$z%?BuD``lj@Hmb#w<F9_M!QsmG@cPBMB0 zwmU{Q7?IJ~@<M9VXHB&^*zHjMw4x5n=kz>7M_#{R;sJTZzj5RLX8iXQc3J$dGr8m* z|K-A$5&NqE{|bV)#Nstk!vovD`hfk*ItSRl;ROl4r+!p<qV<SiG}qWc<2}!0nAfTu z;`{Q9(;imsriF32p!(9-lW7Q?C2f6;YGvAC7P1)+cwH?emXiioLMNR5iJOtCcbuSY zILSOPaeJCHFPRLiw=h~Y5))ma*A(6SqkrfYAJ_1$h)uthM@4Q|$~CSb;hXNkR&dJ) z7`HukiY2NO`?pkD@(h*e8K7adei4XmRnO%-Y0-t=sV;i`M-Svxk85mJ*Jj*8qlJ$s zofy~X2u$1K&UgU>q>jYiv7mJtM>(;x7FC5U!@n1WovORmdqlBoXZNIcyeVUG-549n zpC_qH1zo2%bsHS=9S48Giz#y%H|lcFH)bxp2g~Xh>NewJp|@z3C~+x+8KN+sfkB#h zWA`f%Py9D8GuPX?V$=>x`NzZ#i_k(KkYUrpOvD~>%tnb-d4sOZ<W)h~zXp?biB4tm z#s;n~8+;A3<Y8L!4I0{sd&$Klm-6U$vX^Iz@X3hC*GRRpx!41J4^*p`9fPBsx8wUB zH9g>;zAP)0?O_dU&X|?kXcW&rL4x{iY0c_XWlIjZP>Pj+RUfmJs9SRTd$FVFD}mx< z{KNq_wN3Ono2b#HVWhVtyWuY@$8BYkh&?B23N32_QN34K_T&mE7}$FYw`6~}1;Esp zPMzwZfLi$LM42TnV==@3AhSCfJOtys_!3;*Xd1nJu~Kc}?m=#)jOA?Dx|**D1e#Qr zWo18AjxSw-6f0B4qzEljvYask=WHU_l}+dgEngFbbLudEF4J)?-PFQ4)&k#Nz?PRI z4RftJg&Ryw-IsQ_jB^mt(p-+m8n4V&g74bw+A!VGWM$5zG=MbofrEt&h}UvD53^x0 z4LGKx!CNT9*)+lmx3MNV$&6<P1d~odZET1XyWe^f0l6`^V?)1>{R+9mtcxSLG9Edd zz)1rWLEC=55=+4W-~E^wdxBDpmxbP>yH%eVJE{^3ecR?4XF)?Vo4%Vl14`uDK6WO2 zF9la=lsgj?JDIweH&yY|s|=H<orPJuSal2bIYR69x~C3Du;=9|-bVqs)nV@El^K^k ztcf<3^wGS_(u`aa!3I6v6!`5VqJzwS7GQ>g1+3|oa6`P7hV+h@7ZlC>ep9+Z&q!~m zVcxsVTs)aRbv{Ktqh)HHJdn#89~Yj!gef7apaJexA8BnwpO4^;nTLvnVn^#W_T}W{ zE!vcs20)hNgK#!2xil0NWm8DzC58Sytem!(s?of#GU5o6uzFJc6nNZ}^vO(Sc3=u& z`LR_Y4}2qa00+w$_5R1fYgfd>TM0KWdLKv<qysIiY=N?Z!21`UeE;+t(tfXZ{ZxI} zghXQiW-UXBFUv-M@7;AA<gSylDc|hf+yX}q&##!<w)5og^}G~!M5u*%f2LU0TGGtb z$3kT<DU9!76}L^-jOL}6Wki`2)@K{1!1JXL&1SlRCsdd}Qxo!)AF*-sP+ZEqc+3Um zWOp_gfw?lH!gXbhJ&vwI#tAL<64_tlihZV%z9MK%1VhWcKsQAaSzUl?(#2AwhaDA< zov%WO%>vK{U8M_kkNqC#wty5w^76x{ErGHEWrw%x9e)0o^H##RmL7hAlMznyQ2GgS zn;%`t`l)v^mFx+AbzXwmwo4!#OClI^HyQtrbHG~LC(BmOiCoy!AL_>>P8@*o@58VA zkv;P3p#|s@s0GM|W)V?~JV{F$!!u7&e?*OFnd@Bt799=&4a&jSfzkIQHSA6!DIR2L zJBFOQE@zrw6Y~w;SQQ=QeNM|e-NNcSnCsjS7D6fbY7_A>S<WbdgKMf2dsBXMk>6QK z)vGc`{b8kl8OS0g1LiF$y4pnZ1)pRMHgrsEf8qSOKuA?X5H);0sAbZZ?6P~pWDhF@ zdkuFKYszFUQjqR5V=2xkR+mXW(0-F}|0@t*AO*x1=tj8=!W9EUB=-IR4`Iykw~^4# z$>yluRSDW^Obh2}kf5i^nZG*lChC70goi=s1{t71%=`ok;(-AEY6jm?n>#wTkfg9x zg!isiA-L;;e1>TnP)ssk<x`~qS|cAmvmh@%O*cVLjWeh+P$A&f9u0Y12}3tD^TYHu zt^yi(r11byB}4FdG|+bF^N-|w+V&i`r%f|IhOcNXTYZAUbo)`0d}r_hHCgx;T!e1R zvKuF9_!eJQb^a$rITlD8y8t2T=>MM(&B8C0T&oeDE+D6lxMc8xfk@wK|9IOpSQ2aa zJNf7Ad@=t^zqYL8aZJy^ub&@nO6I#KkoIum+F1PQb8$<FgH06b_2ATDSQ68RGmX9h zY^gxO>hEalfn|X$O2t-8?am74wcXBe%OW}QHN2i_<d`+``fi@%@cMVl<N0NIp%?bK z)8cl`M|D{ahd*3!j^_k=h=-s4r|on0Vs(81Yx_U`)AkVv^)y8@L`a0oqhr?UtRf)Z zSypbZ1@aW{$22&^UIec=N9ca6De&%L?X;ms(&xIAW|W)!Z6GC5hq+87y2{*Q0;c!3 z03#AOHIf~`sR_5s9t(q5z&~m;t*5dqCbSQoS~DHMlH>WGCO;WL@8=}-%T)Fh43bF5 zn+*uRq{9N&a;_+g5vs@j<w*~WEA-O1;lCr9GOrj>4v}=?yBF3Au(4q7yy`sl<Zu#w zhnlA?sA@?=nu6R`K~;JuBV7NNwAxE7yJ`va#{x~*z)i^?GzgrTnq)a)itI<aLoKv| zTmt7pXEL1^4A&Uf-*ZeO#RrV8Y4NUV(9z2ZDtlN>ZRqjzxs|2FNhUk>q;%>qx=HCP zncZMfzLSp;AJWM;Qg8h|I(fjUV$pjTunon%xjo0rL0c`Xzov?Lp1zjRbYeHh=UXus zWVYOlN!Q0(ReLSz3OSqhNdrAFnCTq=i5^@INc00U1{B2R6i8yKaYG6wf7_OCP+zBc zEy|~^d_#n|tdRer5`%b$1-=q*q54>`W5zZ08J!CoN>bDO4{!c6951Ah58%y(;0;Wn z7BM9KGwyF`;vxs|Eo1w%Tf`gHlPWo;=D=fxG#>dWM64~`<Kg0rB0I6$zU&9&eVXS2 zK+mIG4u~(W_F~OOTLP6>m`*Ah#eIHv;8)1-(z3?&b`B!+qRr@Fkyn%+(k^zTRuakB z81iv{t@mQDG$WTaPXz~#giPUa5Z~y$h&ZHGP)FF$e)kM;dnGFjF;V*VupF{`SJ|O^ z<r!odMSRD0ai}jT|2tJZ4lR{xEN)l~wDlzPM+sLIt&iTHN@iSN_BSx1J&`<UOBg0& zfAMi4H=o&w@x_?sPTZi4Qb2Li%V!~UeGPk2`uQj1kOfVseds@%YEI=rIz!u6F=4VA zOw|?U!L1tuxVS#x2vV}-0c{ZvSSxC>#Qz!Y@IU-AJdq!IQ`zBIy$hw2AzBwRdCyTf zELp&M`EZ_B1O*05sW-r2Y3KjIQVg4sOdl{<3i&^<^kd^==8yJ&(Xol^3=aRi3-!;- z-ZidvBcEO!kzD)lrxbR8Yh_c|sytH-wDN=-5@be8+F<yp>F_r)4Opd*Fq4Q{_L!i! znfCY!SJ^f~2&ojMjf9Pgla1fs;kD{;*UBo<JPEl|S_B%UAL}k!(AX?hsJewl39nI7 z_&;pDbyQT}7d{L~DIg*Z0t(V4-5{mXo#T+w3<v{^G=hS(bT<s$-6Gx6F?4r#zIVQ# z_xH#9TQ6(PTEJR&&pG$rbI#t+^X$EC`_oOvpd^}|NY}A4nuVXB#iH%p#7Nzb^zi26 zdlN_Yox$zfXO42T?De*}ObCf@a|wMq9Fg%_z$!LQnEX3(`uw4jU*lsE=tTgusvgZ@ z+j2LTk)oqma5lDW1Rfxmmj!t6LLJOhkW&Q%%qPS8)(@?LCAS8`Y)6Jk?ys`2Fs~Dn z^JgGjXUZ8z)I)n4&kl2DifO7Wn`%v5K$w+Dsh8>I0>Dyf%H?fj_ivtaqWGQtyT+}V z?lo)Y$(0-ddYyu;S;1FmPzkHx&m=l0u-s3O#AMl4Lq+CS5XR4>*b*S%9n%5=UN90w zn#xe<%Y7rKq}w>PCt+4@n00ep=KTu${hvg)={YpE<bXlqKl1n>iJ5;WHl6J?vu zj3mX7CoZbOwlN6T4Y9>b6F6gi=y>1Fa^bF;)(S4h$_C0R4YD@?ivT1Spf54`Z_Bzf zqo!g@)&0~7gAayu0a<hEZMe3;uYoo)(vvq@w?nh^4|LN>?ETm9hO4?9JIc7;``-R9 zR!{4{+3sxXKX3M9Iy)a4tBy=9@!HE6nF`9aAr=L@-T1mSAl=ru(Xr}R7kSW6Un5WA zvuWsF^PXhQ4a;gw9)tS=;P+4~4ad+~zJBH8R>yxUcm7)G!){9JV7uj65!WgCzQ!A& zKz|$7lM=1pux$rCmP0`J6=oyvI_3BeI@YfdojBi-LV?;Nkftj6pizk+HUrDH^3pdN z1C#(u4_KdywG=$}5CdsD(R^;Ik5(}^4j@ha&6V!O+4F@onU-v|CYSmcjd7{#=`Kj5 zi7mIvJy2CVSS;&xPM45pYuEd;MZ(sbR|1B!Y6PQv;szYe7|q;T{`H;ShtbBY`MA4S zoD`46Ozql|<nT?2JZ|K6^vE1zO#64(@#PzrUVsU7A-yYQhxg!b;6Dzvx_dBb$kG?e zNcK!Q1?}^<JOOVCOeEK9QGUx60?7^PNkorWKVeO*)cTjaZSzERFag+OW_^GXj<i>7 z#DMq9g&0A4zoxC=`_EEi&p#Rjx5-^SAvdt}gyM3(EmCqew`-dfNtuz~8Mt8%#I#`* z223Sv8{-x504wmhjeJT%&st?XPp_J_TNKO&U64^AUK}L1jTbYgm7&XzD$|tTHo)HD zZt)Hz=cgwD8hK&gX;kwJlSgbt63-lXw+K|M-Vq9Urv7|CBOmI?*tp}(>~p_!otAo` zKX;Nfx%9h*e*!M_^Q;;+TU9*{Kd&Uh*|uSwt#bYcw~BZU5LBZ8K}81!2&zvsCzvn^ zy1)_J%Sg|UdpGUq<mJs15vz!&ndlAXRtB=R?sIe4y@ncvvu;}{GRvIFTKl)aroF)L zougGu<6(JMZK4GNRTb!&{kgQLJX_nmyz*mN;CExzki}XWTU_a!O+44-d!APhU>vTY z(pDvee;U6mBnOONTdxSJu@k+|zCUgiPBd6<d3)s<AsR$hv7-Fy__-cN!tCK(f$!2D ztVC0C)N3mILlsL;(7hei9?VV3Z}t#)s|gcFy%?n~*+!xz<rGbR>{35*a4=jEq53Th zKw#_h9~7?tz}*W>p=N)_12;*cmXp7gywejtzA^sQ^@UZr$@%Nbb^`8B2hx52BF~um zYdv_{MKU^41MxpiBzeuu1;;uiYg*|osyae5bd#iddHh?6DyZQ2#v$+ngw?vqS#@<g zAFrSb>C{5x`BJgxD{VC}>A9i^O5D*qfHXJorPg0CWc<0xY}MnByto(QN=eoSb1U4* z8$-k#3WH#Y7bA7Hve4S+i6UZgl6)}W(bg;G*AtF-q}p#d$Sc|FxCvl(t(L?Zt}%H~ z@VswHvfi6p5lQYEB5qL#1xx6TT-nK*);^~Y5nGhxa{(^DR?aUcEcb}@@mG@{0_qNJ zhybk<8c)(N@qW+3eStFMBK}ek!L~WWfb-qbntXN)=zX0`Rt<66+-#=N5R1@*8^6Aa zmvSMX@GTQGoiI+*KFnmXl3<$Hi>1;sc21nzT!C@Y7v?lye*wGhid;%!_yFHp$Rdxr zv@x1;pz>P8Q14Z4;m^dU>kap%zu->9YU;MIOyeHF3tHI(P*ICxXbx>8Nvqd*hL(&r zo7b+<@facL_zDuxtcxa*AZUpDz01rL7#RF@K9vyWEDOQRn_jLJd05Q6xBTaUDpQ&P zws+<`o6JZj9V*#;=9U94O>?!}sy3MdVKt^wH>bOZ0ei?6ebuh>w)@k+M1gX>Mr+Tn z@J;tlEyPJ(V=CC>PxBOc%H^_xTn3#l>KLX-lmHlMKA7OE(*jx0$z0|srgHQqc8Y*{ z^i;Sko3MS+uM>XB=-eD0AYO1n#tRleyfEV0!^{CV!CWPB?!0JTbZKhPR|VjaXo5vm z^2G%ghLzlu7`aOeWdREm$2rxyO|tIgAMOA$@$B+HWa4jgsO<7W(xzY@E)xCgerom= z%!UtN_^Bp82qi^6H%1zU>C)=7qWDkO?-S-Kg_U;T=%T*nrj|h<CI1CsB9HcjmCtRp zyq)JzLcB$7MTaD#ps`iTaU!Inu=oRB$Kg3Fb+F#?Hiy>^>jR4Ep1?vl9bKxy9MwhV z#SsW^Ct-$ccKOxnyJV^%;^#=kGs=jdZRhjar-0%);6KH)kfLS4t}jfm>&93v)F|18 z=831nqU@u>!gHCS(a4;`%=Q+$t7*|@X0#q7>8T`BTz_bD{a>SPx7`353cupI1^%{k zxW&7?UF50m_Y`aozDr%7PUv~d$Ylbs>s%Kz^l8CO9QcnhIV!7dG_*u3_g+0YR??kG zrh@(&P4&K}`QLUoUsHq?GcE8-9>cr2@>Y<h!k!Y_gKtuQ&h!V$DVh!-MS68KVG`>O z7u74=KdTB%7*e+?n`U$5YHf!5Hor&<B$*odLo4gi%(u;V8EGlvi|c0i+XmrSJb6h- zd+|@_ZNV?o)@P5--Uje0S$JSw5P6zTh3_+Eo87iv3IyCsClWWN2)+uluW2>^NtR8^ zdO)_uXeG^#uogb!&qP6pup>TL&)mobV%oCm*M!QlBa*QA^DYzs<7H2)4FTh3bAWLS zez3&{l)X@qKe6tcKf8aYsZ};U>Wj6|#dUiu8ekR>BFJ_+UeVRVgv&$61JCJ*Lf5pN zOp?+G$D&?oG9ulswI9Oe9GQj~t>4S#L%&VAZA;viucve3Ew0wh3m|}fuk2|S9^0`J zySW{~qDb(}2mOH?wcrmPtR$66W%1jA_|{y={mMf=c*g1XuQ5TzK%v7=iypbRA!k%J z59&>#ma%D|cCf(HM-sB0I34)kN`R>Oh2jXL*S;TeKU0fI|MZ#^M)Yb8;Ou&cUt=|` zviI;6C-{DbIS9T^1K(v<a0tjHD2;>?ERpL$E>?}byHhd!aBQxY0R1K<NxSD?1A_#L zfjP8FdrTfAxho^{q!Q0B;Z-h*Ieq*aJad5DZBqo!w{7=WOHM`xzDNR*B?+K)_h0<S zg=XLGkA5~*&u{IB=~}IJ89tFE-So`)mfMSS|1a}b#;+WY25HKaKIDz(7r>OU;x=6p zp8iWOA+A8<kQvlPW6@ku$=&OS29KiIH>R+!zK82E`3bh-XAc=KW(0W1k=!d1#P&}B zNZ;2Lkv+f7xdHA~2F@Eb$SL+yo-`?2mtpJYqb-A|iW|&uyc0}*cwlV!2bfh*-<(v1 zedhm$!X_hKfu{xx#pers<QrKtU?>ra%q&TUZh}xw_CUE1=i`Sb-~4fx()zF91f@+d z&OcLD^{n`}6c=}Wz=%=?*fDR-8CL<y^_c`OCZa3~pj>gaxyeSA;Z}pmZP6~=fb-3D zVPf>sl<fHCp79t{kLYF&?lsLQYhbIV1ydl_V;g!^*l)Lo(ra752!tsu+r3E2m6jlM zfdA|Z@c0s9zW9$v0hB8crKBqUIM%BG15%Z2K&nF46KYF5AqEuqY97?CM05*Gq*XbN z;JJV5f98~f82`LM((lO=hvc|_GEwwH;`;%PJC;@NxfKV%un#%PLlb^&F7NRKYdSWF zj7u-NwqAXez5I8xTxS1>@B+Ph@%)pc26V<`3oQLcD7k*mzWwH00P)sPA#yCfzk{m! zy<_kOcOmaJf}RWEWmy2kqsLR)tAO{57*;o%+bHVMeVEV8N33}=>&q@KB}k3|9yqHm zA4#@`GLUW@D!Uy`_^IiOyjb`2_3VnEA-krE<<x;YxC+RLSrTu%YWdWm@itXbZY0{7 zVxHFwG<L!%N)#e;6GlD}HVbfLAr{lCBZ6AdR2MLaCywr?glEgz()K*^Z!j2tKy!Z2 z_DBVNz-9!poTXT>m(S|_iT(oju$wREh4R~Ty|uO+R_4X|{XMd0XmeO}xmoeV*WlIn zlJB^P`-QY#C@yt4h7qAhvFg2e5xCBv-QgJZI(Cc%9f&$e(E)KO4jPc`LX5td_WDl_ ztE=Raxy47-7q1m|s6Uod<taV@Vcr0@+GZ!n>xBCCj*^zMT?VzX*5<RFoPv_Ha?8Bh z%z`9ztrY6>^a5ev7|hGnoG8XjSnywC;OiJ^T$V`9?FqUh?b>YZZp^#^f1YIPUBevr z)hhDv*r;Reqj)h7Hxa4*hsRh0#h6#Oof=l;bpjC}lzgX^!+}mENTsQhM$o>Dc&opw zA9oDdt{qafntLNNG^I2>XuSB)QZU*OTgY7W5~18^k9sn%8MR5WxXkNXas;+MxoHdP zg0Utx$^Bc~zJ6ZNi}bu;@=B@LtIJ1}FVn~0<?bRyQXZo>*9C<lT$hPCuEhJJ6)Y#B zgAHnIHC9?r+%Fj5sbaP9&o^hFKev%8Rt;V_=y*@b*Ft63Np%=0bb@QRUC1cnTP*En z#w1>FT#P>o8*L0_ib({$u9u+Bbf12lEjvgDdwCi5&Qd<>^VaB21KkBc@9O`0Z(scv zN(eqMv>oRHp~R{Ruzx=?Q+d;-8cKJ*iI5-g%ykn)TWG8bP!@4&%w*n|LuZ7)l&t-S z&q&~eaj#B0J^Y#X3>1N_>?@-h?xh+C)JTAyX~?6?s)Nw7Sit#(C?7>ce0_~JOeFt3 z+%zzc)kTPK)|8hqWb1V}Qfrp$_M);qRRsUAi$VX&A*CaawoSWd!KE)})F~_Yltao> z4rPZHsW5%1+JG&q{Y1@Cw?&;4W5@-DZq_!JU-ABV99eHe2(+A#$2x$?8jH9L_Z=~L zgBIpDM(*TK5yleUBYh~WwC#bi5z9%OPu@Gb0`6Nsiaa>AXJg9oxKlKztA%Ht5_N;r z9_6(Bie>Ey3Ma2S;UVzJpV{4HrPwI8ykL;5%<UG<Ge_Q>4J$r(2tQ&ueL%x{KxC|$ zz-VD){-GNq5jLDQQ{`QV`0L8uaUHNGeTQ0t8SiF1ndD|MnIvCnAmLGN?UPSjxxgEL zUd4w3_*Q&A4ScH<@1YKsd?-sLSHbPE4e!5EdS14=#nYwfo_V&8B4RpAzO1aFJ77A+ z=E%(-J@#x0?bFm*>58NiBPtYM75@u6Tg)m-Lf?*iJKsAcQ~1B)6W@me3oL7jRsysh z#N>nKry}R)pU8iQ8(iJN7q0K%qnlGIE>&plLBsYG9$q(SPsTR^531=;`uX)B)+<Bl zgzgT$UlO(5m)vIe#!|2{TAJJ|j=?Cq(q4{WLN#NTeoZ!S&cYEpHdGs2jpHS^EwF4p z7cy}_=UW-gfhC_m@$nMxFRXJu%)D<%8GNWDGc{402T5j^vAq6k!7+iyZE<J+17~uH zBl%M0+Q!Va3Ch?1l$8AJQ0GO)mNXMFZ{>n8omk;*R`2k3zCX970G|JIC;74To)w+< zhlsnk=7M0@UH1hpCF0IirGr%dO1KxVaMn7A3NOM;AK;BozBk%`tH)p_N=$r)F)vjM zlWNN+dUnA>h&QkO!*}d8(Rdc6CKI51el;I0B^lAZsRuLmf4Q1eqtHU<acb;aggqXo zkKZ|U0rTnMcSf=EUl1kw;=YNJERSU*!8-CA(updF_x!zBg(MX+8i-KZMj^U(Pqnw; zjv^YF29u#iMzDJ|mlHhsTR?HPTA3d`%ueFYxSk}>Y$$;~qH8PLCxE9eE*31A;{jZ* zr^vjHz!etIri>kb(N*vAxZRkcJ64j(!cG&<7TD+VV|G!z<@SX{DCqC7=oMO!xrR_G z$nrutOd!Uw$%`Q~V<za8J;mBLH(tNKV<1e-N>C4DRKFHo2mwGd1OS>r^GZuCVc{Qv z=!Td65mLoE({7Ak-v?o;1{vZtyE0u}C|e7}s5g4qWiBIv#_cGwUEDnU<o5wvt4XTX zQ|pJWeJQ<)@~T;|Ht)BDcGp9?A|zTw0*0A8Hd-`Z2zWE<leNl>&>=2ycZ;kfc}lPZ z*~pb`=LZ2ieqpgRNeD6x>srw=YxnQf%eoYNRTEvpi*9entiv1e{DL)+v+L5qWeJ-< z;UhfK#>t@gR9D83Bst^H5;W!S#42e+k}!;uNydN!!&D!Dso(%i^>bS?DWlp-Zs1E% z@FmqB!oG<;Kwe(?SyQ1&oFy?ar#>%D=G@8iDm?M&E%oa}JT05u-hY%qzUGITHaz+I zyB4$*iN$%7=91O$k34zSNRK%2sg@0x!BDEUO-TM3gtTE@Vkoz#)aahQ*rt5Dg{lM| z@-q!y;b$l9mteYeF=WIag<0uko|%CNYO^K5HFtCJqZmNm{G@+Ilk10iT`B#FCW@;r z#^|5Ayf6w8%cK$riQf{>r%%7H+}@|QOeshB3&ffG%<&V&2z5aKn3)HFnXwosYKwpM znFBEMCjWmhbL~@EGw`RhpVXhq)Z6Eqy~5Jbrah#JQ6Bz<k@wq={W#q6CY}GasC`$f zb$6A+E&f3A``q80r(5?hWyY3a(ER?ltZ6fG<qTW=;8_tCA2PAeLM&sZD0sCJx9Oc! ze!lWd7T3}3C5NYPfcTP=Ig<c1WOku<&|KYaGF*Yz8WfQ<=sTyV0#U68U?HeAVc%fG z3-SIs$u_wm>ocz_70W%ynuJ)3I46EMkOS^2!n^V#7JOH2?F~kIIjicrL6*_1kvf=t zQbZGK`J*ONGznupA@yH+ACd_D!JwJy{Kg~J*3TDU*3j>dQvPW;30$qBbP9lCD=!cZ zgLY{y#6gie$a4dAtt#WuR*cGxMon-(1@K4jE$)3}R;!2~XV|WW&yq3E(}2ls@@701 z1+Of7h|r{ES264jV#hbPnO)#t%7Wi^h5->A*mb%VCLvDU?5Oc}aMj=MD(O7J8aB<y zHl#b@ZTtswLrKia&UisYWmeG*IGBU{U0GAB^p3K(f$FGozzH7aDzB3G>9EqMDyeQf zrj4=z^?{TyyW8=jocm^f)OzAP$KY#2WgfoDdM$!)7UoHEJRpgn5f(I7^BTQ(mD{Xn zsysk`>}inC=m3B7-IcYo?MqXtX3~dB^4sHUi8z=8hXBP1Y6H8jh&TX63<Wh=<CL%P z7qfJ&pC;tBMAEjh;<uu{bhl%%f(A`P58pzd%vQS}=}F2rwckQcUt5j6q&x_NniPU~ z7-}|F%6^HhsZ}||zl6?3v|OuPhVpbf##yQJ;+xef_~Kh6kK>gNj4HoYC4MThrxN1{ zux;a^kL>-cP4`b)W2`j!%%VHhBFk&?+wIBITb%87enAVlkA?^Y;=<~@-ezfGYQJ(g zUHUbqgP9&C2t3dj)EqkHA2)h#k?33wTFY&o<dU*dt}H^O1opb@K_A42d}93aGlz=% z+C*gMKeO!IM1*P!>8gzn`eiB`3i+_?082>cd32C4K}#G#1x@_;kM70?g5PAW!D%Oh zU8^lmOU{YItzGB~XC*N1GjHcKWj9IEnl(lSbAn1}LM%OMGBcB~h*MIB(^-)2pa)!Y zB=D2?aBaV7bmHuI^GV7&^h{?@Jf%rdZEd8aerOsXYJl4FLOM^sbMz%8u5U=&(>!Zf z9&mgV{hIp|;-mnm!+1viK|0w4YAs_cgTlSV)5>>fX5$<+KTC)b?STu6g~GL7eg9t` zC+WxNgO=;`+WR5sw#UTR<YW@-H+Mk2m9sXl^mOu+M>PRwgvSkjljGn@UeIYxm?xw6 z<yH39)z9qStJR$DT!ro_&1+M5&4TAtv-8X2*t9VGgBBe9&k4^^=2=RX(0xjXbktD* zhpTF(F9_u;HM)=6ncp*HrLC+qbrdG<h4{qydPVT(Z?L2fsx1R^tcIz@?bTU2Yn`!= zZ+c+w6s!I1CaAvZOY7`B&*$6264#$MmbDSY*O&I3M=jt(dRZ`4Ydl#&KNKy>ym1Ua zk^9rMv+2!KFQ5(mH7n1)PnBQX;&F5SZ*&1->IT`bg3B4K=y8ABy__-$=7NsCQvV2W z^Ywd%4~&Q~S^b+JjZ<bZH&bA5$k%zg*BmEV-vK36t(Ac!$8gdP=DaUkvfV%89{G8r z|D~SG3JBsiNCK(n&dB7HHwyWb$@s#07xAIvpQ|17>E1J+aGy)?Yt27+zK=|_zL}$C zPKFN?N6IT1O8ouz$y#<y06$S&Oiq%|6S!PXK7X5#%bi4pn<8J*dR6}8pAn(?-NTgX zbSC@0Do~e7L-!@4%GUC?#&<D$LY)YV<S)Yn3mn;BN@<*Qo4~ped+fenRQf?cr3u$U zpt4FWkg~bDe^)`;hb6<r7YFdbXkMIo%1Mw+M3qn&9*j7~2OmlbUlFOb#A9rM{+v|z zm<pHU)dvwQvin{stJtU>BV!4Dji5uBt5h>Y@OQaE^QPU)w2sO`70tBlG=q-9R2})F zLko7rW=Fg4<U_2R&*F1*O45E=8r7`CfY3{msRz;xkit77_*q{dn>bF5!bw#8nA1G@ zlN#^vpnZv)7*WaepuQ>&Q|;W9vqiAWG?YGlS_%u>R%0X?^v9oYupZ5pIdPYfo}#Sy z*9M6sZ!Un?SR&E5GG<vQ?a$$tKsUz7EI{{W>T_l>7dm!#52y-%8Ar-$Y_55KI>n3} zGN@Z4S*Twl8KnoHLSA8zLoYj9!`gyqilmYSaND;PZG>%BFWJV2WEg~E3EY=N!bxcu zdIGAv;&hlBccZUIN<{Y-59Mpt&0f9^QW`W3&Zx!1=={1-PUC;Ri-ll{-6;3M%mj+p zy4o6#h?)~)NhA-BjL}TSNsUGJ6JMc02JMO}FvnK_V5q4mqpI#{5SUjdHEPpC>}zr$ z2@6G2@MUX!!YB#Pl5<FY??ACI=Fpc7c`7<3^ZS3&g{DRE10-!<;1-;Dy-3c)omt78 z)`_z-j&NGG&h9Z#M4CTbjPgZ`Ggs@f^huQ(+V2)P33u%lj>Q}3&z4cC$#5~=8~yU% z?7lx0RSZ>ekO$(8x_*xgis1(?Ez6ftWcB!T>D(15iUNX2=vZo7pBHkiIS1FGe?IWD zqbk39fL*cOb>MSb)mi-cDFNP?wy1gV64w~e&C(R9g&2#b=85R(`Zboz#8(?AvM$lH z8BJ3PlhtaKAg@Tahg-Tpsyj#F=x^)tB?j4j+potyzo|RRJ<lQxUDCd@3SLqfBYZWU zhJ?+Qq91q(I^)_7*00`v5YV&#@W*x(f05*>iTAu%sB@D(*?y2foHNJ1m?lKm!$AMI znBC7_sow&4;jfE;7k(J|!k>21d`L0o>WaA?p2t|XSWn_@FxL9(Lf(I^mjk3c%}C#} za?EVYd}G{<KlW3`(dPOzJUcpGxj~Y%ENWAts_!;=bNuUr6>Dm>*-_CpGv1CK5{a{t zA5O&H?$@=TDC$szm8H?;p)g6f_M7&jrrB~TVL<E%sFeS0JR8YLFQ&<{l&Ph)1Yt@h zrEaDBB6UJCxkOcg|C&Rby3?~6s_u8GQ5_<N4|d`d^<Et_zQij;HEh&Rbeo)8;YhX^ zBtDQI)|WW?S7*~%S%aI(Ck6vLQ~~d4y~4I0;fOnVYic(=pJ#cA7dr03(rn-3G^Qlm zfbhQMIGg=wH%3rWjf^jEDu+^vwOs_+Q2xfVz#NTYn(vlxYgRNtn7N6m9qGz^z?j~` zoeBa`#QQ4%(tFT!PCVp$Umf>}iW2xF7lV<H@a?bWcLnUtJO%7IYQ<?jmal6}G(q~j z1<kH6{Ph0-C!SyYB-*6;f!?ot$H?U78cu}w97n{UVOpwKaA^?S+=`m&fg0c#orG0= z$S$O?YtEFhYoevfydE-)r-NSDlK(Jr!}WXC23RtU*T$}S^?I`5twi9l;uO_ld9zGy zcv{Gqr`kSHhGp-Fa*G+PpaGe71fVEm-emqgLtx<<+VbdIx8E%P>at{=Ez6OG#A_+v z_0K5YGY?%$23!vEo4fX1ww>UWsbC2=MQ6qSSi>fT?B=2ra-eBd13p)2Fq0OfUozF6 zJ$*<Aue;YO16!)m<&ia&`2=Jg?ngV6hoHU?u=;xv6=kM}aoe+?*}b8C{Up~EnsXN+ zTm!857zu4BfNVD%%Ocb4My`a;Xkq?{zwHheCXO$m37K?sK4(NQh56I<EbbQLPQ2j5 zlgt7=+69zo!{WnhpMA15JDA38BCyL<mIAw+mEnK8-1JmzTMHc6<vfutei`$=4Q)gK z)b4ALdV5lxhAE2B2}e`zi2Q-xOf;<M-sX2&sScc*Klf|1Z&bOE?`}+V`8o6@&J-&v zH_)NE+{ytDpb|>}DsfdG>hP_Ox-3U2DrQZ!tL}^|{gS5>^U5MJd2AvK@Fq_`e*LpM z&#JMX2om)lE2;l$nh)N^dP5OjY&oSZ*$Cg{%5y;uDV!PT4;L~ly&s7}AHb{U{o%dc zm;8Qwv(Ld%Ms~AF&)t15`ZO~(NK%%GtV{msv5eo8|N3CT`m9<@HE(+xw?GxSpe*En zCSuQ3^4>DM&3|fpnRuTI$ONv*PcIto6NUkSS}PpRTYL1(LLQ1yIvPDW%*WfL&y`c` z%d8ZxqpE?_lr^a*j>?Rt%1&qqzl2Uqc7~zm)eXC~hL;A{{9z_DG!qQ^dLnqM-)n|> z^syDr&s%GWV<8RwSu^TC87{;-t6!c`?2D(Q0#c<!TANj${U?K)hB5tT>+MG_NtLcY zR&!YGX$W`r4~(I+BGEZ=><j%(VW);kxst4Xje=M?)%o0mKwcowZYZN#6aHUbV6;_X zx*{?!Faf!pO-A?e5_DOR!_<5td6J4GOkZEpY#9U>(8exSIQEZLh!BOQMUpJ$Oxz-z z8Zqc=^UVg?9ozLLKfkH1Dmtel44u<vvne#v8;hbGuRt1{i;m?349X!5M*U|H7QMC* z(+SbnD(X4qEnL&%SU2{bUgQl_?Q118jNWAw5!YqwpKTL;zNgHV!wZ?Y@Y0zh0y1NL zXcuv;5Pc$62zhzLjb5<72kU4}jI0$R>7#hwd*nfLVufJ>2{H`&q|mEiz}nPI$>mSG z8vbK4dZ*r%YqNX&i-Q)`#T~tm3)2|<M-f@mQF3w9X>xJ)W)aPVC9xmz@C?hhp)=wq zd7FSW*UtC5LN;{Q+^Wxm)g12#FZ*DzrQ(XeVY!djW%h?dVA$lY!y>v(H{&d^5;=e; z0`3Db)x&BSKE>-ErF)CQ3;{xnwT0_p?NRSZ6;<BfpyRY+-$zA!N7YB<iS}FLW>|(g zO?DxNTwdN`ha(hqx$bs(I7BOPu|`&xc)nZcaj2wtVCi)We|4r1#&I=FyQ;b>^L&gD zXT^q9yUMvN)9MY9bngIGiu7k-rM!@6&A2-uqVE=j<aPn{Al|9P2L<X2FJb+aWV#YW z;DNI@>F;xR7hf$9wK_o9Lv8+xT32U<$)=D|Ysvqj)*J_=9~U;|mLyec>prLV7p*4o z>Lm*>Mk*I+SK%LbNe-t!@z?o>Ox~>SwxH`$#@{T=72q|UHW%j{7XMd@Y!M-RwQvTM zB0B^YA5e;H+nw;(gPe{tTyurxPd;_n_sbs&_qozKq=vfcw#JLuhDrJxbG{9**!t?K zf%7pUyd7;e=&n)1m^9TmH<EVTiH;SDXxr!!=~Z%cG=;T$iw=Lj7PNDqoS)TH-XzIx zjv6b@NiInXv;18{n+w7cN}(Q2cSMFJzv0nifp7V9lWZur@`QOXjYLjdZ=9IFz#gkt z_&q;aCJFD%`PRU~8mgVf&&PmlLhV=1yGwav(v;(B$ZwvF4ik>(+DO)5U=pg^7G9HR zD#zCz2^)6+nI?V|2zjJb(r!O+6Cj?b|7m|H<7~rRu)E1X(Ng?tXHrr#w_6}D59#=| zFv~g@N-`#XPR!^)$=x3%dvVaSdAO?GQ59yl%6^IN!DE+L90+FhDH7ltukUm_#0A<X z=j{ftQS26z?M$|*=30p5Wgso>GUEZ3KEiczKqr|?N@w$zJI0|0CD$ExqgzLpTkz?M zA7(z?aivQ1(A^9H9E_U<xs6G6?kN=z?-nRxpxEz=GQOv3FH(%Qr6IpD=_kW+AM)0$ zdS8M1iHU<my0?5JgBgTJiHK3Ie7+M_&*r_-<Yw@KN@ZE2Np||-+(i*`;L8)!TaRIT z_{GtNIqJU?pl5ATk^$-GD*or^nmO)D_K`FODug}OLZ5W0DlXkXWQ?a4PM8$38bWAH zFzb|Cx%ftev`}rEct#-Efh9D}y2O9Ff&%Rb6`f|Ep&W#rK6^i(=zx)4FWB^KsVPU6 zAZl&8#$)24(uGITfHF=?Q-rBCTZtpp`qw_bPY2(5TfOY(LyxbN+kU$rsVE$Zua@}R zuyPqW^6-&Ea$xrSO8afNQh_wKV@|f=g6rSZwHdT9@gg)ob&y@tJ=X&9Nem=GH=+uW z9j<xm#xGVNd5+XusVU`K5HgFA!Acdh9ZMRgMhJ*dDc8c6OookY#lyCHpM=pW6u$j= ze`i+!gBCI!DIeBExYc@5WD#R%pV=j)!^TK4(%q2KF<I1{1*Pl@S8aVc{T<q6JSPua z*(Vo$l7m5?TR#70!F=~$`iwRz{6Vm`0Ojz`fpR;1x0_R+*r=1H_UVJ5Co#%SIB*gf zV5r`#0y}@ea@p3~Zed9k(ap1so%V^f$F_B+8Eq8-CC&ZO?64@8XzFQckUFaG%s+zK zdJ4BO)jznKziu6LTfM6A%(s92$5;!l@RLwEXfGeFUh_h{H5bM1(_IcvYo6Zz=|D8E z8>dx01|v9|n#6RSo~1tRp(;Bc`>yr&`^^pD29e7BXlV)i?za&c1lqI-TowKK`1F0_ z89}t9#v1n)v%lS}RJEmb?1q|cH3xU}p^!Fd8<3NpADrz`O5<zk96@}&z|eDf#)cVl zh}?O&GCWFgR!-kjQlyT5g>Uy`TZ*;MM;z7P#;aC6(U>eMsQ)X-GRC(4gz%W>!NkCH zzoPDzdd>WabZDySxc}Hzy%Z#WWVf51qPp1fj9)S<H<v%}1CmUre#++tew{`?-}wd* zzs!n<eqZSUp?AQ;Ww^fh)bSH73HjRyqNW0g;Al<%C4!^a6QX|u62YGx0n=PfRP~vn zKh&$*9*a!w7bKNHLm74;ZGMc_HsQUJ`p6d2cBc~_zKOzYYgr8q`TJE{M2Cs3*ya98 zI;)JhaGpVT|BMK;dKQ>s?z@bMlHkS4*CK=o<4j$9-w#Do(+@k{DH2&4%%{j%cW&G5 zJrbmv7GG;~M!L6%VB!uIvP#d-nRB1<)28d4{bl7n1;|k|+Lwu)R2UyC0UFpGBN6rR zTd=v34cu}P{OWlkqhE~{o{EaP{Ft*j`kxOAd@jQB2soNnTWJYywNn_-?o9zmHWOi= z7eH$0DfNYgl)n$ib4y!q^A;yWC8#P)$sWkQi{b)=83-LQw8^0@T@aV+_<W6}y*|rl z$)bYbNU_4-I}IV7l)tnzNtKw>1dm!<%=8B2RrM(ow_j!PB_|9K>#NWgTMak?C6m*g z-;Hfs;aS0vNzyh_JYdpUQU`oC%Y4^Euhx&MT))K@N-_;sPW-FeoxtM`4}!lQwSnXQ z(pIs*y#-l*Q^)nQfLWrXIJO~}ykO^5;majW=Mv4QJbLv1@^%1LSCt-MCR^lvG$#PM zwDcvmmNQt@P^Mq!C#$pBlDVO0DJGnzq~vSicW;ivS=7Rn)r|nd(G8r(L(k9E(rzp- z*bdeITsG<G_?t8gI&C$}7>g&z4-sz+)1O*(ASYnt^<oT2`D8{;V8}y?3ryN=A@t4o zM`UD5-N%+w_X}gGYM^n<?;6a)Rkq;9i8yPWXuhK^K@G^f?H0RCTaEp!Fj7U(B-37l z2T1{*8HB#(jg}Qa-Ipun%M&ttkT|wSMnzBZQjw!(%kf#uO@8j5!kI1q{-rWsc#xg{ z7Own1zFwA>AEBBH@b$LZ73AtENWOkBdWRR_>mvZZUY(Boe`49SeaB%;c+i!rpxI@K z;pmq;SD7k63-Y~BTU!Ti$6H&rmqZzQxs}C&KT>Af9?H<oY@`=x_DLWS&Ie`FXc&=s zY3N@~Iu?~UB9lUnS|-r*$F3tUIXU1r*TazHW-6qg`Mq+I{K(u2kVVgnoEH)t#$!%= zmHWqvKKD(IB`_}`C1zPdR1a#%rc@NgaGmE-TA#+=>PYI8^8TPoLAMwxBjRyP-|O(` z@>gqqD~2Z#q(B84Jk_#2hEx*xz6_n>KeGaq1f?-e{^Pm8W8LJ-5m%Bqb)8J;X6EWm z*`CKHQ+epUoss)7@BhHWs@p05zCcBB&_-F!b0x(U;z*wlG(1Vt<tkP3byZSu2Ey(9 z5+K~(0mAL9h7rbTQncvARj2#olsV{`*mARPq2SEmaDQn7+lg)}PjZ!^b$Sjbp@uBN zV~br@QZPAlAE_ws<^P+LS)7()iCvp%nWU>(rPtY`f*IH^K#;R8eT%P=Y-V>G1R3-$ z@_nlL3(55vWfH9nVnMzLtWI9buL?rgHI?n0Y|-h7*2C`YKNPXqjTKmXr#uP%$^dT= ztz}^p1gEmbM2)YXOA{44R%G@kp@UNa^=gACaC)%ij;{}LE~0vKyw4|4o&riDh{=-7 zo?#Z;BrcY{En#)7Q2AVO0rKhK*P5!AwH0(uVeN?BY4Fm_BtQf;*u5Ngb~EuS*afyT z!X$Mtqtuo9-+aHQ_;G*@Nc%?_N39ZV)pejdtC>zys04=ENdk>>H=kwa*!<^Itt2L) zPbC9RRS6Or`we#*41Co>WH-C_c7s<*=Yx-RZfsj{h_ku(d-bH9^*LI;WP>4Mb%o~7 z60{?qAhMhSPu+yX@+Hq5k)WYcejFj!op6(wEF)hYvl};>^4Q*lw>WRDEXP7o&+6wU zKs7ILx&PWxYe_nS*mV#_j*^KO7vCglSl)w{q=RNRZxRCi?N~4B<;sS)NARrEkbHri z{B825jnh9o${N;*BBMf!sIBi8M;}*>7Z;AM#!o><gs@rAc=SYI#ADfGwQ*yWNU}X_ zI?fh0{77?U+K@kL$948RtBhLNYV+A{&S(iug=JA~W>pe;PD<)|x;(P5R#C3zEITG- z5c8?F`kn~T23->c=(`$(>cOu$2hz2zRT>fysq)5taRE0Ftu?~m?1W-mj0R7s=<!a2 zj#rd?NmcOORXLGjf@ux6V-i1p2gAgS@z0F=k&oEjH5+1-h0(2iU&qQ^LZ08UDP1Y} z%*)lo%#s&n8aISZpT6!?g1FkLy&8($eAb!6UP_Z@NmOeByd<F1)Rpua<V!-7TeZ%P zdA){f5>2+Y)=Yugzbz5gEh3K2=1x($Z=BW*+s4^tmDTv5_X=XFU5!`m&7zQHXwvfT z$;XHMe6?Zb6P4u<x3J&dETL}qJ)N|V<3Vcn6d`W<C&(OCFCa&C4Oye<dQg1tuRjM+ z%d6&~4ZGmk+UmBOtdS#)fYwCx_O@V`+2BiKoW87VV|t?uqn&+dq3Y2PLLg?L#Y-xS z_?dQ-r$+EXi+~}MPvRQVi+C2zAQaIXC+TPure2xW1fu(MolLZesHz`QhbXy>A*<pM zz1)He3)T7@q$u05I6L0Im3`7coC4$~ubsWen&<_NRxIHhW##OXoHfe$?D0>qbW=K? z8(**WM_p*|;vy+frz?kEVLbyIS*k1lniOT(&N>x}bFh%G2_F@mQqkhA191&2`ME0N z!&Fa^-f^8q;--wSVb`Bv!WfE^=gwcz%RYLQ8}K>K<3vxft>5@n75zA`Wt|Cv=u5IX zz~LvtwStE!O?E8n&i6jDt4+kN^SnK~XUSMGQ^4ppMRS_$xVBJ)R^R1(XrlByO*!bt z*NIZeb9(e7+3Xc7WHB&rF4c&gQew6)t3hdG#rYJJ6to{JeMtSRvzZW~lrvrOzvoR` zlZ08G0z7XM<O@lZON0OrF@**I5tFw$Ln30Wuz)@wOOmP*_r(BwR=D=hUP_G}OSSVo zQV&NAndi0KJu||bm_*((o}3!xFGsuS4A7|L!RdK2)YRNMnnVfGy^EUfyxq!55J$T= z(;`W8nzL&+GXefato-#>Wxd-3xYkj~6=x)$l-x5@xhHH+$7f#rd526j=vMAB*LPiN z1e(&N<T?F>c4B;HgG9X<TBDRxS)68O`KfxvHVM-ri8?=h2njQO%5ka*Y!d6w_xcc- zHL*b-j#A!ZulX1qi@(2Enu%bL*i$$t5AZ0kVbOx(R4&L(z|{*ThXr^C^dkzC{4|xq z^;GkbSjd37{DjO#szod<W4@ZreZ@x~(3(JMYyU2HF}e3;6BA4}0ZlwVCBbbV*>`_H zBz#8E4badB0C`!&;WKRC2CB+_Hm<&^0fbV<g+LX)>F3|JFA>q%ABxw5sZlMhl<$hd z&WC#+PVS^n-aMf6fHm0TL38KIIQ&5EI<KV+hp8YETaqdxw*y~mBt8g@Q^#RIyJGu0 z<j=t8?{K>$5~Bn7AiwkP?(&@SjfpvG5^0KWY{r@?V(nmh$6CovUEss+H=?ebxGxsr zFUuzWAwqoZ1)ok20}8fEh=;;cT?v7aBZw>-e<c%YWzg@mVaCICiMiI`mE6{G1I)%| zu|0$x!(N$6^<d8!P2>&Pi+R>;E6>QAxy3oneZw_xm&+4=Y6sHg@9VczmFK77T5P^7 z$N~za!O?v}YNXTgO%$<dr9EL1@+1ZFFp*vN>-vXx8LSe9lqriH^hT1_EF~cP=3^!d z=H$Ae7+=kx-J6*Z?ce$YQ-i*B%7(N)LCB*e4m)~9IAT6dl~B}PF4(_z-1D*j*EWca z?u@1hh4rZhUn(dD9ca21BQFKW-3h|9<#g3n2Yo424CQ<ZkfI8mm(kXu1dD=9e4t$^ zCWeJ^s-x!AX;legB!FcR^OZh`PyhNSI}L)<d1j%$;)}CdMcp-;E?Yds6~DASn|wKn z#nSB*a8~OeI(>PM&NV2Cb<0ealJt0w^e`q{4I;-^y3g;&hHWlR4%`*+Gm8vQK;AFW zt^e0rB3X?BUw{GsBge|rb;GKlJG-U<hx2fL#g6OkewzEjl}B}|VZL=~WgK716CL-i z$CYiJ)M|HI^}E7r(|0Kb3REh$Q?w6eT2;jTrZ@uIkhw3_bqANjtM+ZB4o2>UsQbm7 zmiA)l&4U>GkfEB(fCEa>Dxvy{5lin>R`0No#;l~SMMWwqLJLm++u!f*5V;Z`){P&J z-XyjJ!*z6aw>-}TE`E0@0a4nuSmBQ5msussuf)TZQ&Aq*gV?yYTq%iI?HDjkZ-{+9 zX{pZmJXtaK<>81;>8iWSJik}|)Mm`1r8w$`eWw!!%=^B)#edQd^-6tkq5#3(YGyB5 z8IS}7p+a$Z6)~t4BaVnC0jkxnt21}n=zMvf+vD_gPm#gA{xs1VANoug9PcaPIXIhe z&Wk_B*;r)c4C9x(y^gXU7rt}Zh6?7I&mo@XB;fF@1hjiIT$*rnwDOlHQy5<n_|q)e zR!`wS!tONO9Ysy9S}b*Aslv$zroltjKGuQWKU|BiytAXwm+p0@<!HmR@7y#<<USh+ z_zxAmlk{Q0{zIw#kGaCkRliG7G`4t^jYs^9Tk&PO99o&q_IZ>{OG@8KlE3$E-IoEK z`1Z5cDby$`xv1iauO!Y+nki)IY-t@%=?6%ouN{+!9q#W$?t;cIGj7|%YmFV(|K45q zkBJ8I)F_?K>TW$1R};S#S7T^r2qNtN>K#(55Zlphz20D?g;8-w!_apf$W6tsmaNOR zt3*_8-oL{ld?(fr#_F)4dW+vJ7cWG{W5I8`Ay}R_nIv>q$cDrAH-YKYn9%ItuZ%03 zeurDqXHa12HSMcXqrX4e;@<@u6Ltlj&qh5RlaXuhTh)gQq|xwenSHxSoT@Z8H=VS^ z^}p9!?La#iHoJRK88S?6aBw07bq~tp__l&9mf9@qaA&&D$>Vah&*9nKPo1EO)T+tV zX^rqhO0E}g=Ii-su2yZuov-N*z_(NnN$>QyXq8uYcT}_)NuttZ=QtM1t8nW14`+om z){MK12FM0&%I?WmJNixxYZ@aV;Ul99lezPJ-2S6Yiif(lmoj?9&m5wevt5M>Bz`9A zdOm!<8*YEl7H?mPVzNW59uoQI`@YkHx0591+OhN25(^*_p9!_Us&@**1(~J_mv`Oq zessBX0NQ|6acS53Cpc~^lGpTzI=e+Aq~*LIRZ5v7Ric#$+^?9PLwN-s$r-bp4murg zCbqa9X++o`3ue_QzVe&!2t2vK7pfKLzTM`v1-tEZG@KbW&WuFEjDKQUxZr|*0N;dN zY&vNLZz?k&t96E^YR~sM6H{HZ^4pS%$FB}n8!Jp{FvgZRLqpd;$Gw>9?yVUf&E^RH zd}q*<*i!gp?jL--3}Yjo5N9Arf1B4Y49Yw}T{XjFTkg8kVFKm+t!#B+OOilSO|mhZ zb80aye>xr*vyq#Vaq&dIoV`SkMBX+;kDC&h8Pp^~@6olT*mXAiID}@Wvo<!y!C*g# z+m=`=L8^T+-9ouY@=|gFG6wV>H%-L_eNPoCN4wv5*Fy-}Use8t6LI^zvryd_C|lo1 zAnYHMpuT9V3kg^0*E2Z&Nn@r~boGOHgRskkq9#)iY9f0akV_@v(RY+0yw~9R@U;0> zHT5gyvWR%^!HPHGqvtP}l}4&!b$pDP`Wk>@-oMf-Kf_wgoha*UPaq5qNLvL{oi-Oj z5(caZ#2c<-x<U!d&r%MN^IIMWjQKk(+okc*mQ^%C>Mmclk0zU+Sm$4Ws~?xWe=)&O zvM!zIP?FbC)eBQlY<KwZU%ThGYvRS%8`l;0W|gZdTi3Am<iGJ6?UQ)GoGBkN*y|Bi z6R(1^>tNF&rN8Q0iUlhF%1Fh3IyK&vf-+A26er!!t*&q#KmSbgkY$dU)^E@#x_+BX zR)fh4Gr`+Z3Gf^e;H|`p{i%l{gyUblGmxS?K~Va%#tcIFtk>$3CPXh1NIHVlQ%a#Z zx#5Dc6GX~Coz_p=jd5=qR0!*#c({qv(^&KsgGXxeg4b+T`ZNM0uEi7l*#`&abrXe@ z84PM;J8^C?S`&1%;&ponZKe|k=OXx$as!>)1+pvH2G|2OC(DMHpp4>vj~;UnHQBrD ztg4qZZH=962b5|Gvd2dm#?^E@Tf^x*mRCZw`#JazS^hhZIUuMjmu<kv-$1GljL?K( zJ)KnVA2UJxpa6D#_GH~E;e_Jy>4%M9Wn79nRolY0Vvywyw^^7gh`ef@LE_8J%y)61 z5*pVVIC1BO>HG`x(;oBFGUv9RguA}O%BZ2UR$!WNY`Gw~ZF?PVCG2x0Y`S9oZ=hMf z1kJ_1XEXF;-gVqNmGX`4GUAb+gshnoa4jl<=a5J2QzA&}bHrTLR}<*i@?v93bsj$2 zBiDdz=jW<7HDFxOZ?ItbYxfHKrs5CwdNswFKiutV9Kdri#|H*Cs8`|+F3@Sxim&rA zcL)Te)}eqMQrX@jf+c(dKAR&U*OE1G>Ncakm>&<XQR<xCZ5LzWy2CYEKX#m!Iv5T7 zIY+am8J1>{R@V$E`2BBTXvi3+RzZNoPPU2xS;gT5qB+MdV{DK)*l)~QrNF3dnU=F5 zQohJ;Hq+a+b7eKMn_$DFXduCOScWl^Lx@wMVnOecq)*LgY1C3W28|bU-2NeUGIQ!1 zrK4G=CZ?jRUqjOaR4X0S^)gVpr9WO~3DnbaPV4Tga<_lx%>AelKcE}sI=$+bLl}-P zPPJ(nAL6ClWju@>t1L<<C^xwYdaC)xy+8&#YWh%4bPx6KXdo6gV-!%?^uUI2@E<GZ z%I!OW@{Nr$tY-?0)J(|(gNBKb^P)RHqD&eIAKxDE-rK~(KaDx@*$#TIcO-g062=N< zmfzb)yn8^~JfxngOf5n&p1l!*1c>443LU>&a+;+c#!@;!;NQ&g#ix!J?k~Qy&xR@; z94g0|H0ng{o*Wq_T%9^lTNqv!NFusC!&=`{G6>Jg&|EkfuXl|7q(RKY*Jy}%djG$* zZhD*1_Zcgj%xmM)R)`pOKnZ#6sc(ytzeOHNLsOiUJMFv`=I5MtpMp{cdJbf!g?1Ji zOA3vHuUG|0j$2)E-3m~b-3yc}i`;Q0u`ophA^z-frE|I*-ie&}kmMuSxnudKKw<ia zjoWB4^DnwW#$UcY+^A~54HuaRYxVM`qI1t}N_*W-@<XSD9or(La(#TNeBKNL2l~X( z4*90l*Su!^!49+HfW+Z<>MXmEJep~-%SRGYn$J9>T-1geL)ClV%BJQTqD8&Wop75* z+nEi-7(YBQOltR@1kdX`oZI#0eu#hZo6ux>bZs^;r?;Ek*{pB4yp9-uT4gSeR<b6* zlNx3p`JwTO)w}ueuTENfw5w|$E@%xTQr-h=AYV1_cYNql6>+9+P`!4c<XX&8<8{8x z&>JY$6r`2c#(3T~lwn-RW+R}jr_7)Xg6~~9&Xp=%GOd-hDxC&Nptz!GUjE`#$jZF; zgE@e!Xo}S=h9z#r-Tm2Xic)lKufrtPn)~s_F-yVx7oH@z2%<U{h{jThWgk;;7R!*U z?i@1vst)y-vqdG;Si#<gF?K<Do^d3$wI!M8P~^uieDM_>W4HIp;l?=jHWnlcb&KFh zB8au-NOL&{<3I?hMVI;XO%LE)L=eK?t{X3U7P*pkn$43GhU4E|F(!f&#~pl&q^?FT z>$z>WzpQqQ_8#ip>SgC0;<q}N;FaepD8@lx?-OrUzm9(Kn9}iY&Y8Te=dr~zTkW9e zLkcNxuP=|+rVU3A?jmmMg8Gxj1a$S^zIoDn8^~>&HQE&zDxao&nL(7ZMj*j^NT3q= zNw1AX+g}kig{6IRoZqe}U(EZ9Zn8|sxAl0PMw7qk$`NTEX|(MmL;T8Y$@f?L$Fj1n z?J66JNhf4)r{=h+kyLAVaOFWi(Ebct=JmpGsxgior2xsC+edqGrHFTouQxl&VroJO zSs`LuJ&c_Y^R<16_^FZ7!Co(TDXm$Z|B)b6*%!n4`TgGQD~g&|wGQS&2Wmo{>|Oz$ z2^(^?^vXPiKl&m?-i2!vWKC{oJXy(jPd7ws`MCG|`g_JDkL|QwLuhd0c4}PLesYB2 zlK%JwXBCFJeryqk{DU_0PSLeauKD2U1wsNgd$lCTM{~z8-nXZew>_@U$f$FA?r|@r zW7tqsACso*=Y4#}Iy@`J&-^gqak5A>rQW?b9oyMjMDfSu<eP~7D59>{+F|+7{jgD~ zm3)I#KN-*J7@@OeVCnF%I5gkOEU|@pZwO}Bv3Q)2a9Z4YY;UmQCBNZ$mOl6h#1ALV z`2qMNXJFpu2K|qRS*_&(ympy#xBZ9L+Gg9b3b;;reTDFz;APnO2~K+HHs(gh)#tg- zx^DUQqlFU!dkv7g(ZQhw+;&FNuB9<>@7j|xnA$m?yWm5Zj&UQ>v;Uyc>9>5{jMIJ^ zcL!fK6q#%bY;!9YpgWn#CkU)@SeWaYBcrV{rlX3MsC&o6*lqs{igk5anlz?$-Y#xa zm|UTO`^!`i@JV*VR{!Udh%cMX_x7&*2=%D?c5?v1W#hE)?oNYudOW((*913<G{9wC z-J#R9M7s;gR^H956-8WPh(14u`z)N*oBI1YwYd47FsD}!Yh8l2wN?p7Fp)lc8rd!- zFgEALYN|g(jkI@aZKYU!AtZ5RCiP#IizBwP2;24;c`F?iQPvZ*46OATl1mreJ{{_; zt+qNcs(uv1GKo~VH~0A}j3}W_pF=#Rmss&t!hNz?KKH~Ka^hT?$A^snBMI0hj<mJ! z>gYK(b5Ja*|JOv$l{GcM9yYyA{xr9A7sxH>9c@mM7+vOeE2Z?P8@F!{cls1!P~lJg z@>FM_nH(#NlrW=vIoHD8iN;e~h0{LF$u`8G2_?L^VuSwAu~JWg1*Q8%WZ$RmUdvMM zD?#QM(F!KA?LTCY;VOMN9zyiKaTBXbxDqs(6lh%aWNfA5#qjc?1mu&m4SQ2-p;Ec0 zWBE7ZD>g`4GH6k}_c15way#w<D`&-GNaxPJPpx#8vrAyYGjUSIu!+O2Mg0B%{)xgs zzhyJmt?Kc<^}$<J0SntH3Kt!Eih^PlONyeZfQm+hLWF{Xje^21{pHh7TcIEh6ciNX z2l&_j^#iejm^rzAHT%D>y4b*}eSv|3g8Y#Ef3DF4{?fE`hM3qHTiHT6o&L|S9AE7$ z{*M)GZ0!Gi_n$rKj&{Jp7d8|W-2ac2MBu+rGbbl2I}0esH;C>3edW5gto;i#6ck$Y z|9jMNI4CH<n2rDcuBv7FfA*kPk&A)havTE#kU|1?d4cwoROX~&wQbAyj7zMH3=GZ8 z=<YNFdOkNXFEcH*xJ0igB@O0ISm+{I!61dqVJMYkU~taQOUq0zElMoO%+D(}(l5v_ zC@siM%`4R>-kh~q%qcEOEGaEcN-V-_4r;tT#bQoLetr&Vrbq)liS8?qhe$Ie5Q`~k zIr)htsR$pz(hw+6fe8T&)S)zlYR6(qVsdh7PAV|K^NaM8ON)w9^Gc8t3btsIM=|ss z7DF?06Vp?R^_?<HauW-R@rF52HxiJR#tbb`m=`4GrRLx@M+V6h28LiP<`fj=rx&Fb zqoe{@m^s}{=~DovXIEhE5k)bn7if~NYlx$+r=OcXJYi$WfXGD(FvlT*4?wpi=jWBc VgO-&IBr6Dn1wi}e2mmc+0052v`#k^v literal 0 HcmV?d00001 diff --git a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties new file mode 100644 index 00000000..452f8603 --- /dev/null +++ b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_en_GB.properties @@ -0,0 +1,4 @@ +duniter4j.event.NODE_STARTED=Node started on cluster Duniter4j ES [%s] +duniter4j.event.subject.ERROR=[%s] Error message +duniter4j.event.subject.INFO=[%s] Information message +duniter4j.event.subject.WARN=[%s] Warning message diff --git a/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties new file mode 100644 index 00000000..4ae1466a --- /dev/null +++ b/duniter4j-es-user/src/main/resources/i18n/duniter4j-es-user_fr_FR.properties @@ -0,0 +1,4 @@ +duniter4j.event.NODE_STARTED=Noeud démarré sur le cluster Duniter4j ES [%s] +duniter4j.event.subject.ERROR=%s Message d'erreur +duniter4j.event.subject.INFO=%s Message d'information +duniter4j.event.subject.WARN=%s Message d'avertissement diff --git a/duniter4j-es-user/src/main/resources/plugin-security.policy b/duniter4j-es-user/src/main/resources/plugin-security.policy new file mode 100644 index 00000000..23b556b1 --- /dev/null +++ b/duniter4j-es-user/src/main/resources/plugin-security.policy @@ -0,0 +1,5 @@ +grant codeBase "file:${es.path.home}/plugins/duniter4j-es-user/"{ + permission java.io.FilePermission "/etc/ld.so.conf", "read"; + permission java.io.FilePermission "/etc/ld.so.conf.d/*.conf", "read"; + permission java.io.FilePermission "/usr/local/lib/*", "read"; +}; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 493d3164..60b16e97 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,8 @@ <distribution.snapshotRepository.url>http://nexus.e-is.pro/nexus/content/repositories/duniter4j-snapshots</distribution.snapshotRepository.url> <github.global.server>github</github.global.server> + + <assembly.skip>false</assembly.skip> </properties> <licenses> @@ -99,7 +101,10 @@ <modules> <module>duniter4j-core-shared</module> <module>duniter4j-core-client</module> - <module>duniter4j-elasticsearch</module> + <module>duniter4j-es-core</module> + <module>duniter4j-es-user</module> + <module>duniter4j-es-gchange</module> + <module>duniter4j-es-assembly</module> </modules> <scm> -- GitLab