diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java similarity index 82% rename from duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostAction.java rename to duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java index 00cd50e9a3f8a8752a2addae977acdc930ff3fdb..a40081e59a32e691a052f3c3444fab3dcb1ba6eb 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostIndexAction.java @@ -34,17 +34,17 @@ import org.elasticsearch.rest.*; import static org.elasticsearch.rest.RestRequest.Method.POST; import static org.elasticsearch.rest.RestStatus.OK; -public abstract class AbstractRestPostAction extends BaseRestHandler { +public abstract class AbstractRestPostIndexAction extends BaseRestHandler { private static ESLogger log = null; private final JsonIndexer indexer; - public AbstractRestPostAction(Settings settings, RestController controller, Client client, - String indexName, - String typeName, - JsonIndexer indexer) { + public AbstractRestPostIndexAction(Settings settings, RestController controller, Client client, + String indexName, + String typeName, + JsonIndexer indexer) { super(settings, controller, client); controller.registerHandler(POST, String.format("/%s/%s", indexName, typeName), @@ -57,9 +57,8 @@ public abstract class AbstractRestPostAction extends BaseRestHandler { protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception { try { - String recordId = indexer.handleJson(request.content().toUtf8()); - - restChannel.sendResponse(new BytesRestResponse(OK, recordId)); + String id = indexer.handleJson(request.content().toUtf8()); + restChannel.sendResponse(new BytesRestResponse(OK, id)); } catch(DuniterElasticsearchException | BusinessException e) { log.error(e.getMessage(), e); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java new file mode 100644 index 0000000000000000000000000000000000000000..ad1039b9d3b5256348bfd32ed1a9d2e4f86b8487 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/AbstractRestPostUpdateAction.java @@ -0,0 +1,80 @@ +package org.duniter.elasticsearch.action; + +/* + * #%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.exception.DuniterElasticsearchException; +import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +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, + String indexName, + String typeName, + JsonUpdater updater) { + super(settings, controller, client); + controller.registerHandler(POST, + String.format("/%s/%s/{id}/_update", indexName, typeName), + this); + 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-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java index c9b3bca0fc7c45ff05a9268f1e4a7edee815ff57..19f6062d6629fa84be0be2a81e92d505116236ed 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/RestModule.java @@ -36,6 +36,8 @@ import org.duniter.elasticsearch.action.security.RestSecurityGetChallengeAction; import org.duniter.elasticsearch.action.site.RestCesiumConfigAction; import org.duniter.elasticsearch.action.user.RestUserProfileIndexAction; import org.duniter.elasticsearch.action.user.RestUserProfileUpdateAction; +import org.duniter.elasticsearch.action.user.RestUserSettingsIndexAction; +import org.duniter.elasticsearch.action.user.RestUserSettingsUpdateAction; import org.elasticsearch.common.inject.AbstractModule; import org.elasticsearch.common.inject.Module; @@ -59,6 +61,8 @@ public class RestModule extends AbstractModule implements Module { // User bind(RestUserProfileIndexAction.class).asEagerSingleton(); bind(RestUserProfileUpdateAction.class).asEagerSingleton(); + bind(RestUserSettingsIndexAction.class).asEagerSingleton(); + bind(RestUserSettingsUpdateAction.class).asEagerSingleton(); // Authentication bind(RestSecurityGetChallengeAction.class).asEagerSingleton(); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageIndexAction.java index 045bf1a285d2ab796f0a8859f743da8afd69b720..0db480ef0f189e31f31ad7b766780217a2f449c0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageIndexAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/message/RestMessageIndexAction.java @@ -22,14 +22,14 @@ package org.duniter.elasticsearch.action.message; * #L% */ -import org.duniter.elasticsearch.action.AbstractRestPostAction; +import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; import org.duniter.elasticsearch.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 RestMessageIndexAction extends AbstractRestPostAction { +public class RestMessageIndexAction extends AbstractRestPostIndexAction { @Inject public RestMessageIndexAction(Settings settings, RestController controller, Client client, final MessageService service) { diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java index 4d37e42f141b4db609cfbcbc8a3c9420adc2ed2a..a305415c0d217728583e00cf667c3b7badd595a0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java @@ -22,50 +22,20 @@ package org.duniter.elasticsearch.action.user; * #L% */ -import org.duniter.core.exception.BusinessException; -import org.duniter.elasticsearch.exception.DuniterElasticsearchException; -import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; -import org.duniter.elasticsearch.service.MarketService; +import org.duniter.elasticsearch.action.AbstractRestPostIndexAction; import org.duniter.elasticsearch.service.UserService; 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.*; +import org.elasticsearch.rest.RestController; -import static org.elasticsearch.rest.RestRequest.Method.POST; -import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; -import static org.elasticsearch.rest.RestStatus.OK; - -public class RestUserProfileIndexAction extends BaseRestHandler { - - private static final ESLogger log = ESLoggerFactory.getLogger(RestUserProfileIndexAction.class.getName()); - - private UserService service; +public class RestUserProfileIndexAction extends AbstractRestPostIndexAction { @Inject public RestUserProfileIndexAction(Settings settings, RestController controller, Client client, UserService service) { - super(settings, controller, client); - controller.registerHandler(POST, "/user/profile", this); - this.service = service; - } - - @Override - protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception { - - try { - String profileId = service.indexProfileFromJson(request.content().toUtf8()); - - restChannel.sendResponse(new BytesRestResponse(OK, profileId)); - } - catch(DuniterElasticsearchException | BusinessException e) { - log.error(e.getMessage(), e); - restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); - } - catch(Exception e) { - log.error(e.getMessage(), e); - } + super(settings, controller, client, + UserService.INDEX, + UserService.PROFILE_TYPE, + json -> service.indexProfileFromJson(json)); } - } \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java index 75174db28093f026bbb09233185ae4d637798439..2c978fb550d5ac73b412ed40f68b6f1e5c8c59f2 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java @@ -22,48 +22,21 @@ package org.duniter.elasticsearch.action.user; * #L% */ -import org.duniter.core.exception.BusinessException; -import org.duniter.elasticsearch.exception.DuniterElasticsearchException; -import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +import org.duniter.elasticsearch.action.AbstractRestPostUpdateAction; import org.duniter.elasticsearch.service.UserService; 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.*; +import org.elasticsearch.rest.RestController; -import static org.elasticsearch.rest.RestRequest.Method.POST; -import static org.elasticsearch.rest.RestStatus.OK; - -public class RestUserProfileUpdateAction extends BaseRestHandler { - - private static final ESLogger log = ESLoggerFactory.getLogger(RestUserProfileUpdateAction.class.getName()); - - private UserService service; +public class RestUserProfileUpdateAction extends AbstractRestPostUpdateAction { @Inject public RestUserProfileUpdateAction(Settings settings, RestController controller, Client client, UserService service) { - super(settings, controller, client); - controller.registerHandler(POST, "/user/profile/{id}/_update", this); - this.service = service; - } - - @Override - protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception { - String id = request.param("id"); - - try { - service.updateProfileFromJson(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); - } + super(settings, controller, client, + UserService.INDEX, + UserService.PROFILE_TYPE, + (json, id) -> service.updateProfileFromJson(json, id)); } } \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java new file mode 100644 index 0000000000000000000000000000000000000000..179216d917df9a40dc1be7c3a8e7b6debd7d40a6 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsIndexAction.java @@ -0,0 +1,42 @@ +package org.duniter.elasticsearch.action.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.action.AbstractRestPostIndexAction; +import org.duniter.elasticsearch.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, final UserService service) { + super(settings, controller, client, + UserService.INDEX, + UserService.SETTINGS_TYPE, + json -> service.indexSettingsFromJson(json)); + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java new file mode 100644 index 0000000000000000000000000000000000000000..5174ad115a4efea9baef961ba7dad24bdba48e04 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserSettingsUpdateAction.java @@ -0,0 +1,42 @@ +package org.duniter.elasticsearch.action.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.action.AbstractRestPostUpdateAction; +import org.duniter.elasticsearch.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, final UserService service) { + super(settings, controller, client, + 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/UserService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java index 0a8086b902f802ac6b4227641984eef4df1f0de6..cc079c45e97919dfffdfe511b6d2442ce571aa2f 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java @@ -49,7 +49,7 @@ public class UserService extends AbstractService { public static final String INDEX = "user"; public static final String PROFILE_TYPE = "profile"; - private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\""; + public static final String SETTINGS_TYPE = "settings"; @Inject public UserService(Client client, @@ -88,6 +88,7 @@ public class UserService extends AbstractService { .build(); createIndexRequestBuilder.setSettings(indexSettings); createIndexRequestBuilder.addMapping(PROFILE_TYPE, createProfileType()); + createIndexRequestBuilder.addMapping(SETTINGS_TYPE, createSettingsType()); createIndexRequestBuilder.execute().actionGet(); return this; @@ -146,6 +147,51 @@ public class UserService extends AbstractService { .execute().actionGet(); } + /** + * + * Index an user settings + * @param settingsJson settings, as JSON string + * @return the settings id (=the issuer pubkey) + */ + public String indexSettingsFromJson(String settingsJson) { + + JsonNode actualObj = readAndVerifyIssuerSignature(settingsJson); + String issuer = getIssuer(actualObj); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing a user settings from issuer [%s]", issuer.substring(0, 8))); + } + + IndexResponse response = client.prepareIndex(INDEX, SETTINGS_TYPE) + .setSource(settingsJson) + .setId(issuer) // always use the issuer pubkey as id + .setRefresh(false) + .execute().actionGet(); + return response.getId(); + } + + /** + * Update user settings + * @param settingsJson settings, as JSON string + */ + public void updateSettingsFromJson(String settingsJson, String id) { + + JsonNode actualObj = readAndVerifyIssuerSignature(settingsJson); + String issuer = getIssuer(actualObj); + + if (!Objects.equals(issuer, id)) { + throw new AccessDeniedException(String.format("Could not update this document: not issuer.")); + } + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing a user settings from issuer [%s]", issuer.substring(0, 8))); + } + + UpdateResponse response = client.prepareUpdate(INDEX, SETTINGS_TYPE, issuer) + .setDoc(settingsJson) + .execute().actionGet(); + } + + /* -- Internal methods -- */ @@ -174,8 +220,8 @@ public class UserService extends AbstractService { .field("type", "integer") .endObject() - // pubkey - .startObject("pubkey") + // issuer + .startObject("issuer") .field("type", "string") .field("index", "not_analyzed") .endObject() @@ -244,4 +290,42 @@ public class UserService extends AbstractService { } } + public XContentBuilder createSettingsType() { + + try { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(SETTINGS_TYPE) + .startObject("properties") + + // time + .startObject("time") + .field("type", "integer") + .endObject() + + // issuer + .startObject("issuer") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // nonce + .startObject("nonce") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // content + .startObject("content") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + .endObject() + .endObject().endObject(); + + return mapping; + } + catch(IOException ioe) { + throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, SETTINGS_TYPE, ioe.getMessage()), ioe); + } + } } diff --git a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml b/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml index f5d2b60a627b21fc44f18927ce3d8405c4e1d116..71fef2db4c67bdb60a6f96fc87d53390650f2fb6 100644 --- a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml +++ b/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml @@ -15,13 +15,13 @@ # Use a descriptive name for your cluster: # # cluster.name: my-application -cluster.name: duniter4j-elasticsearch +cluster.name: duniter4j-elasticsearch-TEST # # ------------------------------------ Node ------------------------------------ # # Use a descriptive name for the node: # -node.name: EIS-DEV +node.name: TEST # # Add custom attributes to the node: # @@ -73,8 +73,8 @@ http.cors.enabled: true # 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: ["192.168.0.5", "192.168.0.28"] -#discovery.zen.ping.unicast.hosts: ["127.0.0.1", ""] +#discovery.zen.ping.unicast.hosts: ["192.168.0.5", "192.168.0.28"] +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): #