diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java new file mode 100644 index 0000000000000000000000000000000000000000..c09fe0d125212863155ea662b67b071fa39676f1 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java @@ -0,0 +1,61 @@ +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% + */ + +/** + * Created by blavenie on 01/03/16. + */ +public class DeleteRecord extends Record { + + public static final String PROPERTY_INDEX="index"; + public static final String PROPERTY_TYPE="type"; + public static final String PROPERTY_ID="id"; + + private String index; + private String type; + private String id; + + public String getIndex() { + return index; + } + + public void setIndex(String index) { + this.index = index; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java new file mode 100644 index 0000000000000000000000000000000000000000..e5fd532cbe270bff4e83d5afe9fa11109d0c0189 --- /dev/null +++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/UserProfile.java @@ -0,0 +1,34 @@ +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% + */ + +/** + * Created by blavenie on 01/03/16. + */ +public class UserProfile extends Record { + + public static final String PROPERTY_TITLE = "title"; + public static final String PROPERTY_DESCRIPTION="description"; + public static final String PROPERTY_CITY="city"; + +} 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 8ef4f7a8c09f5977f4b139c47eae55178ce000ff..ab35ea1c50996b4311bfe97633fc2ab0f0d53d6d 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 @@ -23,23 +23,45 @@ package org.duniter.elasticsearch.action; */ import org.duniter.elasticsearch.action.currency.RestCurrencyIndexAction; +import org.duniter.elasticsearch.action.history.RestHistoryDeleteIndexAction; import org.duniter.elasticsearch.action.market.RestMarketCommentIndexAction; +import org.duniter.elasticsearch.action.market.RestMarketCommentUpdateAction; import org.duniter.elasticsearch.action.market.RestMarketRecordIndexAction; +import org.duniter.elasticsearch.action.market.RestMarketRecordUpdateAction; import org.duniter.elasticsearch.action.registry.RestRegistryRecordIndexAction; import org.duniter.elasticsearch.action.security.RestSecurityAuthAction; import org.duniter.elasticsearch.action.security.RestSecurityGetChallengeAction; +import org.duniter.elasticsearch.action.user.RestUserProfileIndexAction; +import org.duniter.elasticsearch.action.user.RestUserProfileUpdateAction; 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(); + + // Market bind(RestMarketRecordIndexAction.class).asEagerSingleton(); + bind(RestMarketRecordUpdateAction.class).asEagerSingleton(); bind(RestMarketCommentIndexAction.class).asEagerSingleton(); + bind(RestMarketCommentUpdateAction.class).asEagerSingleton(); + + // Registry bind(RestRegistryRecordIndexAction.class).asEagerSingleton(); + // User + bind(RestUserProfileIndexAction.class).asEagerSingleton(); + bind(RestUserProfileUpdateAction.class).asEagerSingleton(); + + // Authentication bind(RestSecurityGetChallengeAction.class).asEagerSingleton(); bind(RestSecurityAuthAction.class).asEagerSingleton(); + + // History + bind(RestHistoryDeleteIndexAction.class).asEagerSingleton(); + } } \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java new file mode 100644 index 0000000000000000000000000000000000000000..faea221db9ce82346ecbcf3bfaee3b06915a5412 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/history/RestHistoryDeleteIndexAction.java @@ -0,0 +1,71 @@ +package org.duniter.elasticsearch.action.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.core.exception.BusinessException; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +import org.duniter.elasticsearch.service.HistoryService; +import org.duniter.elasticsearch.service.MarketService; +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 static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestStatus.BAD_REQUEST; +import static org.elasticsearch.rest.RestStatus.OK; + +public class RestHistoryDeleteIndexAction extends BaseRestHandler { + + private static final ESLogger log = ESLoggerFactory.getLogger(RestHistoryDeleteIndexAction.class.getName()); + + private HistoryService service; + + @Inject + public RestHistoryDeleteIndexAction(Settings settings, RestController controller, Client client, HistoryService service) { + super(settings, controller, client); + controller.registerHandler(POST, "/history/delete", this); + this.service = service; + } + + @Override + protected void handleRequest(final RestRequest request, RestChannel restChannel, Client client) throws Exception { + + try { + String recordId = service.indexDeleteFromJson(request.content().toUtf8()); + + restChannel.sendResponse(new BytesRestResponse(OK, recordId)); + } + catch(DuniterElasticsearchException | BusinessException e) { + log.error(e.getMessage(), e); + restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); + } + catch(Exception e) { + log.error(e.getMessage(), e); + } + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java index d43148bc299060125628dbcdf3967f843ca61cf2..28ff0a9c5b344546835bb1aefca0a15c40c89ab5 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentIndexAction.java @@ -23,6 +23,8 @@ package org.duniter.elasticsearch.action.market; */ 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.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; @@ -56,9 +58,9 @@ public class RestMarketCommentIndexAction extends BaseRestHandler { restChannel.sendResponse(new BytesRestResponse(OK, recordId)); } - catch(BusinessException e) { + catch(DuniterElasticsearchException | BusinessException e) { log.error(e.getMessage(), e); - restChannel.sendResponse(new BytesRestResponse(BAD_REQUEST, String.format("{error: {ucode: 'XXX', message:'%s'}}", e.getMessage()))); + restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); } catch(Exception e) { log.error(e.getMessage(), e); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java new file mode 100644 index 0000000000000000000000000000000000000000..213198cc6a05a0f12b7848f2ab7f063cd65e060f --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketCommentUpdateAction.java @@ -0,0 +1,69 @@ +package org.duniter.elasticsearch.action.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.core.exception.BusinessException; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +import org.duniter.elasticsearch.service.MarketService; +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 static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestStatus.OK; + +public class RestMarketCommentUpdateAction extends BaseRestHandler { + + private static final ESLogger log = ESLoggerFactory.getLogger(RestMarketCommentUpdateAction.class.getName()); + + private MarketService service; + + @Inject + public RestMarketCommentUpdateAction(Settings settings, RestController controller, Client client, MarketService service) { + super(settings, controller, client); + controller.registerHandler(POST, "/market/comment/{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.updateCommentFromJson(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); + } + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java index 67ba7708c23f6b3cda743a7e3e8da2b1a2209fa9..bcc4bb2b0baf03b88eb34cae3ad2fff5694864f7 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordIndexAction.java @@ -23,6 +23,8 @@ package org.duniter.elasticsearch.action.market; */ 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.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; @@ -56,9 +58,9 @@ public class RestMarketRecordIndexAction extends BaseRestHandler { restChannel.sendResponse(new BytesRestResponse(OK, recordId)); } - catch(BusinessException e) { + catch(DuniterElasticsearchException | BusinessException e) { log.error(e.getMessage(), e); - restChannel.sendResponse(new BytesRestResponse(BAD_REQUEST, String.format("{error: {ucode: 'XXX', message:'%s'}}", e.getMessage()))); + restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); } catch(Exception e) { log.error(e.getMessage(), e); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java new file mode 100644 index 0000000000000000000000000000000000000000..bde8c3dd6f2f0f3f01c2ef45e0e4cd1f387371c7 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/market/RestMarketRecordUpdateAction.java @@ -0,0 +1,68 @@ +package org.duniter.elasticsearch.action.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.core.exception.BusinessException; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +import org.duniter.elasticsearch.service.MarketService; +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 static org.elasticsearch.rest.RestRequest.Method.POST; +import static org.elasticsearch.rest.RestStatus.OK; + +public class RestMarketRecordUpdateAction extends BaseRestHandler { + + private static final ESLogger log = ESLoggerFactory.getLogger(RestMarketRecordUpdateAction.class.getName()); + + private MarketService service; + + @Inject + public RestMarketRecordUpdateAction(Settings settings, RestController controller, Client client, MarketService service) { + super(settings, controller, client); + controller.registerHandler(POST, "/market/record/{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.updateRecordFromJson(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); + } + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java index 858b4368df7e477e9866349da54ffdc449f4500c..92e2d6de3752882a9809b1e48c0d85c7b265563f 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/registry/RestRegistryRecordIndexAction.java @@ -23,6 +23,8 @@ package org.duniter.elasticsearch.action.registry; */ import org.duniter.core.exception.BusinessException; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; import org.duniter.elasticsearch.service.RegistryService; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; @@ -56,9 +58,9 @@ public class RestRegistryRecordIndexAction extends BaseRestHandler { restChannel.sendResponse(new BytesRestResponse(OK, recordId)); } - catch(BusinessException e) { + catch(DuniterElasticsearchException | BusinessException e) { log.error(e.getMessage(), e); - restChannel.sendResponse(new BytesRestResponse(BAD_REQUEST, String.format("{error: {ucode: 'XXX', message:'%s'}}", e.getMessage()))); + restChannel.sendResponse(new XContentThrowableRestResponse(request, e)); } catch(Exception e) { log.error(e.getMessage(), e); 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 new file mode 100644 index 0000000000000000000000000000000000000000..4d37e42f141b4db609cfbcbc8a3c9420adc2ed2a --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileIndexAction.java @@ -0,0 +1,71 @@ +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.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.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 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; + + @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); + } + } + +} \ 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 new file mode 100644 index 0000000000000000000000000000000000000000..75174db28093f026bbb09233185ae4d637798439 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/action/user/RestUserProfileUpdateAction.java @@ -0,0 +1,69 @@ +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.core.exception.BusinessException; +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.duniter.elasticsearch.rest.XContentThrowableRestResponse; +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 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; + + @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); + } + } + +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java index c4603b3e446e10627c34ebf19afac4d3383fbb1b..b2b0b67800d4ae62380ef845aa516fd3b9638afe 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/AccessDeniedException.java @@ -23,27 +23,27 @@ package org.duniter.elasticsearch.exception; */ -import org.duniter.core.exception.BusinessException; +import org.elasticsearch.rest.RestStatus; /** * Created by Benoit on 03/04/2015. */ -public class AccessDeniedException extends BusinessException{ +public class AccessDeniedException extends DuniterElasticsearchException{ - public AccessDeniedException() { - super(); + public AccessDeniedException(Throwable cause) { + super(cause); } - public AccessDeniedException(String message, Throwable cause) { - super(message, cause); + public AccessDeniedException(String msg, Object... args) { + super(msg, args); } - public AccessDeniedException(String message) { - super(message); + public AccessDeniedException(String msg, Throwable cause, Object... args) { + super(msg, args, cause); } - public AccessDeniedException(Throwable cause) { - super(cause); + @Override + public RestStatus status() { + return RestStatus.FORBIDDEN; } - } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java new file mode 100644 index 0000000000000000000000000000000000000000..bab585576c45a75405d1850ba391267d1e733811 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuniterElasticsearchException.java @@ -0,0 +1,23 @@ +package org.duniter.elasticsearch.exception; + +import org.elasticsearch.ElasticsearchException; + +/** + * Created by blavenie on 28/07/16. + */ +public abstract class DuniterElasticsearchException extends ElasticsearchException { + + + public DuniterElasticsearchException(Throwable cause) { + super(cause); + } + + public DuniterElasticsearchException(String msg, Object... args) { + super(msg, args); + } + + public DuniterElasticsearchException(String msg, Throwable cause, Object... args) { + super(msg, args, cause); + } + +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java index c72d3888afc3e93674021e2a894c6ff6fbb9a852..58d230e432452829df2e18ebf828a9a59cc232ed 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/DuplicateIndexIdException.java @@ -24,27 +24,29 @@ package org.duniter.elasticsearch.exception; import org.duniter.core.exception.BusinessException; +import org.elasticsearch.rest.RestStatus; /** * * Created by Benoit on 03/04/2015. */ -public class DuplicateIndexIdException extends BusinessException{ +public class DuplicateIndexIdException extends DuniterElasticsearchException{ - public DuplicateIndexIdException() { - super(); + public DuplicateIndexIdException(Throwable cause) { + super(cause); } - public DuplicateIndexIdException(String message, Throwable cause) { - super(message, cause); + public DuplicateIndexIdException(String msg, Object... args) { + super(msg, args); } - public DuplicateIndexIdException(String message) { - super(message); + public DuplicateIndexIdException(String msg, Throwable cause, Object... args) { + super(msg, args, cause); } - public DuplicateIndexIdException(Throwable cause) { - super(cause); - } + @Override + public RestStatus status() { + return RestStatus.BAD_REQUEST; + } } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java index 63bba9c6bcb3b35537c542aecf38624e1d2ab92e..4d47e5141b06ff7678ebc3f211e3f819f55a5cb0 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidFormatException.java @@ -23,24 +23,26 @@ package org.duniter.elasticsearch.exception; */ import org.duniter.core.exception.BusinessException; +import org.elasticsearch.rest.RestStatus; /** * Created by blavenie on 01/03/16. */ -public class InvalidFormatException extends BusinessException { - public InvalidFormatException() { - super(); +public class InvalidFormatException extends DuniterElasticsearchException { + public InvalidFormatException(Throwable cause) { + super(cause); } - public InvalidFormatException(String message, Throwable cause) { - super(message, cause); + public InvalidFormatException(String msg, Object... args) { + super(msg, args); } - public InvalidFormatException(String message) { - super(message); + public InvalidFormatException(String msg, Throwable cause, Object... args) { + super(msg, args, cause); } - public InvalidFormatException(Throwable cause) { - super(cause); + @Override + public RestStatus status() { + return RestStatus.BAD_REQUEST; } } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java index 09a83041fa3cb432d18dbfe6d4282150e42d694d..19476ae5e73cb9298ea0a32ca4a5e08ab41d5ebe 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/InvalidSignatureException.java @@ -23,27 +23,29 @@ package org.duniter.elasticsearch.exception; */ -import org.duniter.core.exception.BusinessException; +import org.elasticsearch.rest.RestStatus; /** * Created by Benoit on 03/04/2015. */ -public class InvalidSignatureException extends BusinessException { +public class InvalidSignatureException extends DuniterElasticsearchException { - public InvalidSignatureException() { - super(); + public InvalidSignatureException(Throwable cause) { + super(cause); } - public InvalidSignatureException(String message, Throwable cause) { - super(message, cause); + public InvalidSignatureException(String msg, Object... args) { + super(msg, args); } - public InvalidSignatureException(String message) { - super(message); + public InvalidSignatureException(String msg, Throwable cause, Object... args) { + super(msg, args, cause); } - public InvalidSignatureException(Throwable cause) { - super(cause); + + @Override + public RestStatus status() { + return RestStatus.BAD_REQUEST; } } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java new file mode 100644 index 0000000000000000000000000000000000000000..94d00b4b274892c4e2fbc33da71171e2ba98766f --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/exception/NotFoundException.java @@ -0,0 +1,50 @@ +package org.duniter.elasticsearch.exception; + +/* + * #%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.exception.BusinessException; +import org.elasticsearch.rest.RestStatus; + +/** + * Created by Benoit on 03/04/2015. + */ +public class NotFoundException extends DuniterElasticsearchException{ + + public NotFoundException(Throwable cause) { + super(cause); + } + + public NotFoundException(String msg, Object... args) { + super(msg, args); + } + + public NotFoundException(String msg, Throwable cause, Object... args) { + super(msg, args, cause); + } + + @Override + public RestStatus status() { + return RestStatus.NOT_FOUND; + } +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java index 2840c839a79d1b37ce91344997ddbc57229e8c74..d0b8745c45e76fc1d90084ca5ee830cedffc0ec4 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/node/DuniterNode.java @@ -24,10 +24,7 @@ package org.duniter.elasticsearch.node; import org.duniter.core.client.model.local.Peer; import org.duniter.elasticsearch.PluginSettings; -import org.duniter.elasticsearch.service.BlockchainService; -import org.duniter.elasticsearch.service.MarketService; -import org.duniter.elasticsearch.service.MessageService; -import org.duniter.elasticsearch.service.RegistryService; +import org.duniter.elasticsearch.service.*; import org.duniter.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.common.component.AbstractLifecycleComponent; import org.elasticsearch.common.inject.Inject; @@ -90,9 +87,18 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { .deleteIndex() .createIndexIfNotExists(); + injector.getInstance(UserService.class) + .deleteIndex() + .createIndexIfNotExists(); + + injector.getInstance(HistoryService.class) + .deleteIndex() + .createIndexIfNotExists(); + injector.getInstance(BlockchainService.class) .indexLastBlocks(peer); + if (logger.isInfoEnabled()) { logger.info("Reloading all Duniter indices... [OK]"); } @@ -103,8 +109,10 @@ public class DuniterNode extends AbstractLifecycleComponent<DuniterNode> { } 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(); if (logger.isInfoEnabled()) { logger.info("Checking Duniter indices... [OK]"); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..451dc57c71330bdbaf6562131ae14b6aeb2b065c --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/RestXContentBuilder.java @@ -0,0 +1,39 @@ +package org.duniter.elasticsearch.rest; + +import org.elasticsearch.common.io.stream.BytesStreamOutput; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.rest.RestRequest; + +import java.io.IOException; + +public class RestXContentBuilder { + + public static XContentBuilder restContentBuilder(RestRequest request) throws IOException { + XContentType contentType = XContentType.fromRestContentType(request.param("format", request.header("Content-Type"))); + if (contentType == null) { + // default to JSON + contentType = XContentType.JSON; + } + XContentBuilder builder = new XContentBuilder(XContentFactory.xContent(contentType), + new BytesStreamOutput()); + if (request.paramAsBoolean("pretty", false)) { + builder.prettyPrint().lfAtEnd(); + } + String casing = request.param("case"); + if (casing != null && "camelCase".equals(casing)) { + builder.fieldCaseConversion(XContentBuilder.FieldCaseConversion.CAMELCASE); + } else { + // we expect all REST interfaces to write results in underscore casing, so + // no need for double casing + builder.fieldCaseConversion(XContentBuilder.FieldCaseConversion.NONE); + } + return builder; + } + + public static XContentBuilder emptyBuilder(RestRequest request) throws IOException { + return restContentBuilder(request).startObject().endObject(); + } + +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..c79558e73ce5b6acdfea094aa05428d802d912ee --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentRestResponse.java @@ -0,0 +1,16 @@ +package org.duniter.elasticsearch.rest; + +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.rest.BytesRestResponse; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.RestStatus; + +import java.io.IOException; + +public class XContentRestResponse extends BytesRestResponse { + + public XContentRestResponse(RestRequest request, RestStatus status, XContentBuilder builder) throws IOException { + super(status, builder); + } + +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java new file mode 100644 index 0000000000000000000000000000000000000000..e759e7a22d65ebcbd7d658ab1d7bb50af2597aad --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/rest/XContentThrowableRestResponse.java @@ -0,0 +1,63 @@ +package org.duniter.elasticsearch.rest; + +import org.duniter.elasticsearch.exception.DuniterElasticsearchException; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.rest.RestRequest; +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 { + + public XContentThrowableRestResponse(RestRequest request, Throwable t) throws IOException { + this(request, ((t instanceof ElasticsearchException) ? ((ElasticsearchException) t).status() : ((t instanceof DuniterElasticsearchException) ? ((DuniterElasticsearchException) t).status() : RestStatus.INTERNAL_SERVER_ERROR)), t); + } + + public XContentThrowableRestResponse(RestRequest request, RestStatus status, Throwable t) throws IOException { + super(request, status, convert(request, status, t)); + } + + private static XContentBuilder convert(RestRequest request, RestStatus status, Throwable t) throws IOException { + XContentBuilder builder = restContentBuilder(request).startObject() + .field("error", detailedMessage(t)) + .field("status", status.getStatus()); + if (t != null && request.paramAsBoolean("error_trace", false)) { + builder.startObject("error_trace"); + boolean first = true; + while (t != null) { + if (!first) { + builder.startObject("cause"); + } + buildThrowable(t, builder); + if (!first) { + builder.endObject(); + } + t = t.getCause(); + first = false; + } + builder.endObject(); + } + builder.endObject(); + return builder; + } + + private static void buildThrowable(Throwable t, XContentBuilder builder) throws IOException { + builder.field("message", t.getMessage()); + for (StackTraceElement stElement : t.getStackTrace()) { + builder.startObject("at") + .field("class", stElement.getClassName()) + .field("method", stElement.getMethodName()); + if (stElement.getFileName() != null) { + builder.field("file", stElement.getFileName()); + } + if (stElement.getLineNumber() >= 0) { + builder.field("line", stElement.getLineNumber()); + } + builder.endObject(); + } + } +} \ No newline at end of file diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java index ff61ad270b05cc670a8709ca8ed70cb2cfe2ab50..bfaee8c3c4b7938727c9f8e699fc50147265d01c 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/security/token/SecurityTokenStore.java @@ -48,8 +48,8 @@ public class SecurityTokenStore { @Inject public SecurityTokenStore(Settings settings) { - this.prefix = settings.get("duniter4j.auth.token.prefix", "duniter4j-"); - this.validityDurationInSeconds = settings.getAsInt("duniter4j.auth.tokenValidityDuration", 30*60 /*30min*/ ); + this.prefix = settings.get("duniter.auth.token.prefix", "duniter-"); + this.validityDurationInSeconds = settings.getAsInt("duniter.auth.tokenValidityDuration", 600 /*= 10min*/ ); this.tokenCache = initGeneratedMessageCache(); } diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java index 0c6ba4dbf2744482d8d5085711d07e86bb703d0a..d06108794fcd756b0628763c7595fdca478d299d 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/AbstractService.java @@ -23,55 +23,68 @@ package org.duniter.elasticsearch.service; */ +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Preconditions; +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.exception.TechnicalException; +import org.duniter.core.service.CryptoService; import org.duniter.core.util.StringUtils; import org.duniter.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.exception.AccessDeniedException; +import org.duniter.elasticsearch.exception.InvalidFormatException; +import org.duniter.elasticsearch.exception.InvalidSignatureException; +import org.duniter.elasticsearch.exception.NotFoundException; +import org.elasticsearch.ElasticsearchException; import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequestBuilder; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse; import org.elasticsearch.action.bulk.BulkRequest; -import org.elasticsearch.client.AdminClient; +import org.elasticsearch.action.get.GetResponse; import org.elasticsearch.client.Client; import org.elasticsearch.client.Requests; -import org.elasticsearch.client.transport.TransportClient; import org.elasticsearch.common.bytes.BytesArray; -import org.elasticsearch.common.component.AbstractComponent; -import org.elasticsearch.common.component.Lifecycle; -import org.elasticsearch.common.component.LifecycleComponent; -import org.elasticsearch.common.component.LifecycleListener; import org.elasticsearch.common.inject.Inject; -import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.logging.ESLogger; -import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.logging.Loggers; -import org.elasticsearch.common.settings.Settings; -import org.elasticsearch.common.transport.InetSocketTransportAddress; import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentFactory; -import org.elasticsearch.node.NodeBuilder; import java.io.*; -import java.net.InetAddress; -import java.net.UnknownHostException; +import java.util.Objects; +import java.util.Set; /** * Created by Benoit on 08/04/2015. */ public abstract class AbstractService implements Bean { + protected static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\""; + protected static final String REGEX_WORD_SEPARATOR = "[-\\t@# ]+"; + protected static final String REGEX_SPACE = "[\\t\\n\\r ]+"; + protected final ESLogger logger; protected final Client client; protected final PluginSettings pluginSettings; protected final ObjectMapper objectMapper; + protected final CryptoService cryptoService; + + public AbstractService(Client client, PluginSettings pluginSettings, CryptoService cryptoService) { + this.logger = Loggers.getLogger(getClass()); + this.client = client; + this.pluginSettings = pluginSettings; + this.cryptoService = cryptoService; + this.objectMapper = new ObjectMapper(); + } - @Inject public AbstractService(Client client, PluginSettings pluginSettings) { this.logger = Loggers.getLogger(getClass()); this.client = client; this.pluginSettings = pluginSettings; + this.cryptoService = null; this.objectMapper = new ObjectMapper(); } @@ -114,6 +127,65 @@ public abstract class AbstractService implements Bean { } } + protected JsonNode readAndVerifyIssuerSignature(String recordJson) throws ElasticsearchException { + + try { + JsonNode actualObj = objectMapper.readTree(recordJson); + readAndVerifyIssuerSignature(recordJson, actualObj); + return actualObj; + } + catch(IOException | JsonSyntaxException e) { + throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); + } + } + + protected void readAndVerifyIssuerSignature(String recordJson, JsonNode actualObj) throws ElasticsearchException { + + try { + Set<String> fieldNames = ImmutableSet.copyOf(actualObj.fieldNames()); + if (!fieldNames.contains(Record.PROPERTY_ISSUER) + || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) { + throw new InvalidFormatException(String.format("Invalid record JSON format. Required fields [%s,%s]", Record.PROPERTY_ISSUER, Record.PROPERTY_SIGNATURE)); + } + String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText(); + String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText(); + + String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "") + .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), ""); + + if (!cryptoService.verify(recordNoSign, signature, issuer)) { + throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign); + } + + // TODO: check issuer is in the WOT ? + } + catch(JsonSyntaxException e) { + throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); + } + } + + protected void checkSameDocumentIssuer(String index, String type, String id, String expectedIssuer) throws ElasticsearchException { + + GetResponse response = client.prepareGet(index, type, id) + .setFields(Record.PROPERTY_ISSUER) + .execute().actionGet(); + boolean failed = !response.isExists(); + if (failed) { + throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, id)); + } else { + String docIssuer = (String)response.getFields().get(Record.PROPERTY_ISSUER).getValue(); + if (!Objects.equals(expectedIssuer, docIssuer)) { + throw new AccessDeniedException(String.format("Could not delete this document: not issuer.")); + } + } + } + + + + protected String getIssuer(JsonNode actualObj) { + return actualObj.get(Record.PROPERTY_ISSUER).asText(); + } + protected void bulkFromClasspathFile(String classpathFile, String indexName, String indexType) { InputStream is = null; try { diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java new file mode 100644 index 0000000000000000000000000000000000000000..3995e2d363af61909b90ef9d72124339db02c605 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/HistoryService.java @@ -0,0 +1,226 @@ +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 com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import org.duniter.core.client.model.elasticsearch.DeleteRecord; +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.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.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentFactory; + +import java.io.IOException; + +/** + * Created by Benoit on 30/03/2015. + */ +public class HistoryService extends AbstractService { + + public static final String INDEX = "history"; + public static final String DELETE_TYPE = "delete"; + + @Inject + public HistoryService(Client client, PluginSettings settings, CryptoService cryptoService) { + super(client, settings, cryptoService); + } + + /** + * Delete blockchain index, and all data + * @throws JsonProcessingException + */ + public HistoryService deleteIndex() { + deleteIndexIfExists(INDEX); + return this; + } + + + public boolean existsIndex() { + return super.existsIndex(INDEX); + } + + /** + * Create index need for blockchain registry, if need + */ + public HistoryService createIndexIfNotExists() { + try { + if (!existsIndex(INDEX)) { + createIndex(); + } + } + catch(JsonProcessingException e) { + throw new TechnicalException(String.format("Error while creating index [%s]", INDEX)); + } + + return this; + } + + /** + * Create index need for category registry + * @throws JsonProcessingException + */ + public HistoryService createIndex() throws JsonProcessingException { + logger.info(String.format("Creating index [%s/%s]", INDEX, DELETE_TYPE)); + + CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX); + Settings indexSettings = Settings.settingsBuilder() + .put("number_of_shards", 2) + .put("number_of_replicas", 1) + //.put("analyzer", createDefaultAnalyzer()) + .build(); + createIndexRequestBuilder.setSettings(indexSettings); + createIndexRequestBuilder.addMapping(DELETE_TYPE, createDeleteType()); + createIndexRequestBuilder.execute().actionGet(); + + return this; + } + + + public String indexDeleteFromJson(String recordJson) { + JsonNode actualObj = readAndVerifyIssuerSignature(recordJson); + String issuer = actualObj.get(DeleteRecord.PROPERTY_ISSUER).asText(); + + String index = actualObj.get(DeleteRecord.PROPERTY_INDEX).asText(); + String type = actualObj.get(DeleteRecord.PROPERTY_TYPE).asText(); + String id = actualObj.get(DeleteRecord.PROPERTY_ID).asText(); + + if (!existsIndex(index)) { + throw new NotFoundException(String.format("Index [%s] not exists.", index)); + } + + // Check document issuer + checkSameDocumentIssuer(index, type, id, issuer); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Deleting document [%s/%s/%s] - issuer [%s]", index, type, id, issuer.substring(0, 8))); + } + + // Add deletion to history + IndexResponse response = client.prepareIndex(INDEX, DELETE_TYPE) + .setSource(recordJson) + .setRefresh(false) + .execute().actionGet(); + + // Delete the document + client.prepareDelete(index, type, id).execute().actionGet(); + + return response.getId(); + } + + + /* -- Internal methods -- */ + + + public XContentBuilder createDeleteType() { + try { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(DELETE_TYPE) + .startObject("properties") + + // index + .startObject("index") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // type + .startObject("type") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // id + .startObject("id") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // time + .startObject("time") + .field("type", "integer") + .endObject() + + .endObject() + .endObject().endObject(); + + return mapping; + } + catch(IOException ioe) { + throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, DELETE_TYPE, ioe.getMessage()), ioe); + } + } + + public XContentBuilder createRecordCommentType() { + String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer(); + + try { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(DELETE_TYPE) + .startObject("properties") + + // issuer + .startObject("issuer") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // time + .startObject("time") + .field("type", "integer") + .endObject() + + // message + .startObject("message") + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + + // record + .startObject("record") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // reply to + .startObject("reply_to") + .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, DELETE_TYPE, ioe.getMessage()), ioe); + } + } + +} diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java index 1795ce20d0ca4f110c68f22e8ad1bb892f52b16c..417b4207e3d7db785ad9e4ab6932b9455a6559f3 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MarketService.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.google.common.collect.Sets; import com.google.gson.JsonSyntaxException; +import org.duniter.core.client.model.elasticsearch.DeleteRecord; import org.duniter.core.client.model.elasticsearch.Record; import org.duniter.core.client.service.bma.WotRemoteService; import org.duniter.core.exception.TechnicalException; @@ -37,6 +38,7 @@ 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.update.UpdateResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.settings.Settings; @@ -57,15 +59,12 @@ public class MarketService extends AbstractService { public static final String RECORD_COMMENT_TYPE = "comment"; private static final String CATEGORIES_BULK_CLASSPATH_FILE = "market-categories-bulk-insert.json"; - private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\""; - private CryptoService cryptoService; private WotRemoteService wotRemoteService; @Inject public MarketService(Client client, PluginSettings settings, CryptoService cryptoService, WotRemoteService wotRemoteService) { - super(client, settings); - this.cryptoService = cryptoService; + super(client, settings, cryptoService); this.wotRemoteService = wotRemoteService; } @@ -145,90 +144,69 @@ public class MarketService extends AbstractService { public String indexRecordFromJson(String recordJson) { - try { - JsonNode actualObj = objectMapper.readTree(recordJson); - Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames()); - if (!fieldNames.contains(Record.PROPERTY_ISSUER) - || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) { - throw new InvalidFormatException("Invalid record JSON format. Required fields [issuer,signature]"); - } - String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText(); + JsonNode actualObj = readAndVerifyIssuerSignature(recordJson); + String issuer = getIssuer(actualObj); - String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing market record from issuer [%s]", issuer.substring(0, 8))); + } - String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "") - .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), ""); + IndexResponse response = client.prepareIndex(INDEX, RECORD_TYPE) + .setSource(recordJson) + .setRefresh(false) + .execute().actionGet(); - if (!cryptoService.verify(recordNoSign, signature, issuer)) { - throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign); - } + return response.getId(); + } - // TODO : check if issuer is a valid member - //wotRemoteService.getRequirments(); + public void updateRecordFromJson(String recordJson, String id) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8))); - } + JsonNode actualObj = readAndVerifyIssuerSignature(recordJson); + String issuer = getIssuer(actualObj); - } - catch(IOException | JsonSyntaxException e) { - throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); - } + // Check same document issuer + checkSameDocumentIssuer(INDEX, RECORD_TYPE, id, issuer); - // Preparing indexBlocksFromNode - IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE) - .setSource(recordJson); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Updating market record [%s] from issuer [%s]", id, issuer.substring(0, 8))); + } - // Execute indexBlocksFromNode - IndexResponse response = indexRequest - .setRefresh(false) + client.prepareUpdate(INDEX, RECORD_TYPE, id) + .setDoc(recordJson) .execute().actionGet(); - - return response.getId(); } public String indexCommentFromJson(String commentJson) { - try { - JsonNode actualObj = objectMapper.readTree(commentJson); - Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames()); - if (!fieldNames.contains(Record.PROPERTY_ISSUER) - || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) { - throw new InvalidFormatException(String.format("Invalid comment JSON format. Required fields [%s,%s]", - Record.PROPERTY_ISSUER, Record.PROPERTY_SIGNATURE)); - } - String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText(); - String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText(); + JsonNode actualObj = readAndVerifyIssuerSignature(commentJson); + String issuer = getIssuer(actualObj); - String recordNoSign = commentJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "") - .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), ""); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing a comment from issuer [%s]", issuer.substring(0, 8))); + } - if (!cryptoService.verify(recordNoSign, signature, issuer)) { - throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign); - } + IndexResponse response = client.prepareIndex(INDEX, RECORD_COMMENT_TYPE) + .setSource(commentJson) + .setRefresh(false) + .execute().actionGet(); + return response.getId(); + } - // TODO : check if issuer is a valid member - //wotRemoteService.getRequirments(); + public void updateCommentFromJson(String commentJson, String id) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("Indexing a comment from issuer [%s]", issuer.substring(0, 8))); - } + JsonNode actualObj = readAndVerifyIssuerSignature(commentJson); + String issuer = getIssuer(actualObj); - } - catch(IOException | JsonSyntaxException e) { - throw new InvalidFormatException("Invalid comment JSON: " + e.getMessage(), e); - } + // Check same document issuer + checkSameDocumentIssuer(INDEX, RECORD_COMMENT_TYPE, id, issuer); - // Preparing indexBlocksFromNode - IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_COMMENT_TYPE) - .setSource(commentJson); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Updating comment [%s] from issuer [%s]", id, issuer.substring(0, 8))); + } // Execute indexBlocksFromNode - IndexResponse response = indexRequest - .setRefresh(false) + client.prepareUpdate(INDEX, RECORD_COMMENT_TYPE, id) .execute().actionGet(); - - return response.getId(); } public void fillRecordCategories() { diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java index 1f0a236953576772c028a6ef730abf79ba118722..b201a228bd43874bec311dc55ebb3510d1e3c53d 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/MessageService.java @@ -56,14 +56,10 @@ public class MessageService extends AbstractService { private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\""; - private CryptoService cryptoService; - private WotRemoteService wotRemoteService; @Inject - public MessageService(Client client, PluginSettings settings, CryptoService cryptoService, WotRemoteService wotRemoteService) { - super(client, settings); - this.cryptoService = cryptoService; - this.wotRemoteService = wotRemoteService; + public MessageService(Client client, PluginSettings settings, CryptoService cryptoService) { + super(client, settings, cryptoService); } /** @@ -118,41 +114,15 @@ public class MessageService extends AbstractService { public String indexRecordFromJson(String recordJson) { - try { - JsonNode actualObj = objectMapper.readTree(recordJson); - Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames()); - if (!fieldNames.contains(MessageRecord.PROPERTY_ISSUER) - || !fieldNames.contains(MessageRecord.PROPERTY_SIGNATURE)) { - throw new InvalidFormatException(String.format("Invalid record JSON format. Required fields [%s,%s]", MessageRecord.PROPERTY_ISSUER, MessageRecord.PROPERTY_SIGNATURE)); - } - String issuer = actualObj.get(MessageRecord.PROPERTY_ISSUER).asText(); - - String signature = actualObj.get(MessageRecord.PROPERTY_SIGNATURE).asText(); - - String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, MessageRecord.PROPERTY_SIGNATURE), ""); - - if (!cryptoService.verify(recordNoSign, signature, issuer)) { - throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign); - } - - // TODO : check if issuer is a valid member - //wotRemoteService.getRequirments(); - - if (logger.isDebugEnabled()) { - logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8))); - } + JsonNode actualObj = readAndVerifyIssuerSignature(recordJson); + String issuer = getIssuer(actualObj); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8))); } - catch(IOException | JsonSyntaxException e) { - throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); - } - - // Preparing indexBlocksFromNode - IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE) - .setSource(recordJson); - // Execute indexBlocksFromNode - IndexResponse response = indexRequest + IndexResponse response = client.prepareIndex(INDEX, RECORD_TYPE) + .setSource(recordJson) .setRefresh(false) .execute().actionGet(); diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java index c3324b964ae4d6b2ff8a909b370423331d1a2699..ceb4aa2bd832b8c4ae03d07c4aca0b9e858031fe 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/RegistryService.java @@ -31,7 +31,6 @@ import com.google.common.collect.Lists; import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.duniter.core.client.model.bma.BlockchainBlock; import org.duniter.core.client.model.bma.BlockchainParameters; @@ -50,8 +49,6 @@ import org.duniter.elasticsearch.exception.AccessDeniedException; import org.duniter.elasticsearch.exception.DuplicateIndexIdException; import org.duniter.elasticsearch.exception.InvalidFormatException; import org.duniter.elasticsearch.exception.InvalidSignatureException; -import org.duniter.elasticsearch.model.SearchResult; -import org.duniter.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.index.IndexRequestBuilder; import org.elasticsearch.action.index.IndexResponse; @@ -59,8 +56,6 @@ import org.elasticsearch.action.search.SearchPhaseExecutionException; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.SearchType; -import org.elasticsearch.action.suggest.SuggestRequestBuilder; -import org.elasticsearch.action.suggest.SuggestResponse; import org.elasticsearch.client.Client; import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.xcontent.XContentBuilder; @@ -68,15 +63,11 @@ 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.highlight.HighlightField; -import org.elasticsearch.search.sort.SortOrder; -import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Set; @@ -89,14 +80,9 @@ public class RegistryService extends AbstractService { public static final String RECORD_TYPE = "record"; public static final String RECORD_CATEGORY_TYPE = "category"; public static final String CURRENCY_TYPE = "currency"; - private static final String JSON_STRING_PROPERTY_REGEX = "[,]?[\"\\s\\n\\r]*%s[\"]?[\\s\\n\\r]*:[\\s\\n\\r]*\"[^\"]+\""; private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json"; - public static final String REGEX_WORD_SEPARATOR = "[-\\t@# ]+"; - public static final String REGEX_SPACE = "[\\t\\n\\r ]+"; private final Gson gson; - private WotRemoteService wotRemoteService; - private CryptoService cryptoService; private BlockchainRemoteService blockchainRemoteService; @Inject @@ -105,10 +91,8 @@ public class RegistryService extends AbstractService { WotRemoteService wotRemoteService, CryptoService cryptoService, BlockchainRemoteService blockchainRemoteService) { - super(client, settings); + super(client, settings, cryptoService); gson = GsonUtils.newBuilder().create(); - this.wotRemoteService = wotRemoteService; - this.cryptoService = cryptoService; this.blockchainRemoteService = blockchainRemoteService; } @@ -181,51 +165,20 @@ public class RegistryService extends AbstractService { */ public String indexRecordFromJson(String recordJson) { - try { - ObjectMapper mapper = new ObjectMapper(); - JsonNode actualObj = mapper.readTree(recordJson); - Set<String> fieldNames = Sets.newHashSet(actualObj.fieldNames()); - if (!fieldNames.contains(Record.PROPERTY_ISSUER) - || !fieldNames.contains(Record.PROPERTY_SIGNATURE)) { - throw new InvalidFormatException("Invalid record JSON format. Required fields [issuer,signature]"); - } - String issuer = actualObj.get(Record.PROPERTY_ISSUER).asText(); - String signature = actualObj.get(Record.PROPERTY_SIGNATURE).asText(); - - String recordNoSign = recordJson.replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_SIGNATURE), "") - .replaceAll(String.format(JSON_STRING_PROPERTY_REGEX, Record.PROPERTY_HASH), ""); - - if (!cryptoService.verify(recordNoSign, signature, issuer)) { - throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign); - } - - // TODO verify hash - //if (!cryptoService.verifyHash(recordNoSign, signature, issuer)) { - // throw new InvalidSignatureException("Invalid signature for JSON string: " + recordNoSign); - //} + JsonNode actualObj = readAndVerifyIssuerSignature(recordJson); + String issuer = getIssuer(actualObj); - if (logger.isDebugEnabled()) { - logger.debug(String.format("Indexing a record from issuer [%s]", issuer.substring(0, 8))); - } - - } - catch(IOException | JsonSyntaxException e) { - throw new InvalidFormatException("Invalid record JSON: " + e.getMessage(), e); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing a registry record from issuer [%s]", issuer.substring(0, 8))); } - // Preparing indexBlocksFromNode - IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE) - .setSource(recordJson); - - // Execute indexBlocksFromNode - IndexResponse response = indexRequest + IndexResponse response = client.prepareIndex(INDEX, RECORD_TYPE) + .setSource(recordJson) .setRefresh(false) .execute().actionGet(); - return response.getId(); } - public void insertRecordFromBulkFile(File bulkFile) { if (logger.isDebugEnabled()) { @@ -442,10 +395,24 @@ public class RegistryService extends AbstractService { .field("type", "geo_point") .endObject() - // avatar - .startObject("avatar") + // thumbnail + .startObject("thumbnail") + .field("type", "attachment") + .startObject("fields") // src + .startObject("content") // title + .field("index", "no") + .endObject() + .startObject("title") // title .field("type", "string") - .field("index", "not_analyzed") + .field("store", "no") + .endObject() + .startObject("author") // title + .field("store", "no") + .endObject() + .startObject("content_type") // title + .field("store", "yes") + .endObject() + .endObject() .endObject() // categories diff --git a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java index c22f32c32b5035c65adb12d10117bdb3fb7bca20..8dd1a7c9db1a4721a9af84d7c1f91975429b0d65 100644 --- a/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java @@ -52,6 +52,7 @@ public class ServiceModule extends AbstractModule implements Module { bind(MarketService.class); bind(BlockchainService.class); bind(MessageService.class); + bind(HistoryService.class); // Duniter Client API beans bindWithLocator(BlockchainRemoteService.class); 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 new file mode 100644 index 0000000000000000000000000000000000000000..5967d7fc6d63a197f718e4d8ad0634548491fbf3 --- /dev/null +++ b/duniter4j-elasticsearch/src/main/java/org/duniter/elasticsearch/service/UserService.java @@ -0,0 +1,246 @@ +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 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.elasticsearch.PluginSettings; +import org.duniter.elasticsearch.exception.AccessDeniedException; +import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; +import org.elasticsearch.action.index.IndexResponse; +import org.elasticsearch.action.update.UpdateResponse; +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; +import java.util.Objects; + +/** + * Created by Benoit on 30/03/2015. + */ +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]*\"[^\"]+\""; + + @Inject + public UserService(Client client, + PluginSettings settings, + CryptoService cryptoService) { + super(client, settings,cryptoService); + } + + /** + * Create index need for blockchain registry, if need + */ + public UserService createIndexIfNotExists() { + try { + if (!existsIndex(INDEX)) { + createIndex(); + } + } + catch(JsonProcessingException e) { + throw new TechnicalException(String.format("Error while creating index [%s]", INDEX)); + } + return this; + } + + /** + * Create index for registry + * @throws JsonProcessingException + */ + public UserService 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(PROFILE_TYPE, createProfileType()); + createIndexRequestBuilder.execute().actionGet(); + + return this; + } + + public UserService deleteIndex() { + deleteIndexIfExists(INDEX); + return this; + } + + public boolean existsIndex() { + return super.existsIndex(INDEX); + } + + /** + * + * Index an user profile + * @param profileJson + * @return the profile id + */ + public String indexProfileFromJson(String profileJson) { + + JsonNode actualObj = readAndVerifyIssuerSignature(profileJson); + String issuer = getIssuer(actualObj); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Indexing a user profile from issuer [%s]", issuer.substring(0, 8))); + } + + IndexResponse response = client.prepareIndex(INDEX, PROFILE_TYPE) + .setSource(profileJson) + .setRefresh(false) + .execute().actionGet(); + return response.getId(); + } + + /** + * Update an user profile + * @param profileJson + */ + public void updateProfileFromJson(String profileJson, String id) { + + JsonNode actualObj = readAndVerifyIssuerSignature(profileJson); + 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 profile from issuer [%s]", issuer.substring(0, 8))); + } + + UpdateResponse response = client.prepareUpdate(INDEX, PROFILE_TYPE, issuer) + .setDoc(profileJson) + .execute().actionGet(); + } + + + /* -- Internal methods -- */ + + + public XContentBuilder createProfileType() { + String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer(); + + try { + XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(PROFILE_TYPE) + .startObject("properties") + + // title + .startObject("title") + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + + // description + .startObject("description") + .field("type", "string") + .field("analyzer", stringAnalyzer) + .endObject() + + // time + .startObject("time") + .field("type", "integer") + .endObject() + + // pubkey + .startObject("pubkey") + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + + // location + .startObject("location") + .field("type", "string") + .endObject() + + // geoPoint + .startObject("geoPoint") + .field("type", "geo_point") + .endObject() + + // avatar + .startObject("avatar") + .field("type", "attachment") + .startObject("fields") // fields + .startObject("content") // content + .field("index", "no") + .endObject() + .startObject("title") // title + .field("type", "string") + .field("store", "no") + .endObject() + .startObject("author") // author + .field("store", "no") + .endObject() + .startObject("content_type") // content_type + .field("store", "yes") + .endObject() + .endObject() + .endObject() + + // social networks + .startObject("socials") + .field("type", "nested") + .field("dynamic", "false") + .startObject("properties") + .startObject("type") // type + .field("type", "string") + .field("index", "not_analyzed") + .endObject() + .startObject("url") // url + .field("type", "string") + .field("index", "not_analyzed") + .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, PROFILE_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 ce3e43751395bf626de86429284649abbe07f8f1..c799994ae98bc358eac840c0a87c112104bfedfa 100644 --- a/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml +++ b/duniter4j-elasticsearch/src/test/es-home/config/elasticsearch.yml @@ -106,8 +106,14 @@ duniter.port: 9330 duniter.string.analyzer: french -duniter.indices.reload: true +#duniter.indices.reload: true #duniter.dev.enable: true -#script.groovy.sandbox.enabled: true \ No newline at end of file +#script.groovy.sandbox.enabled: true + +# Security token prefix (default: 'duniter-') +#duniter.auth.token.prefix: duniter- + +# Token validity duration, in seconds (default: 600) +duniter.auth.tokenValidityDuration: 3600 # = 1hour \ No newline at end of file diff --git a/ucoinj.iml b/ucoinj.iml deleted file mode 100644 index 66b3d797e48191074a1789a5dd24dc3776d9ec53..0000000000000000000000000000000000000000 --- a/ucoinj.iml +++ /dev/null @@ -1,12 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4"> - <component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_5" inherit-compiler-output="false"> - <output url="file://$MODULE_DIR$/target/classes" /> - <output-test url="file://$MODULE_DIR$/target/test-classes" /> - <content url="file://$MODULE_DIR$"> - <excludeFolder url="file://$MODULE_DIR$/target" /> - </content> - <orderEntry type="inheritedJdk" /> - <orderEntry type="sourceFolder" forTests="false" /> - </component> -</module> \ No newline at end of file