diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-02-05 12:00:25 +0100 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2016-02-05 14:24:49 +0100 |
commit | 57ec3d37f7420a262b2724b1e7bbe958ad13b5d6 (patch) | |
tree | 56e9f0c66082759cf6430e5548f94c45bca82a84 | |
parent | bea23239548d8f1abb7ecf5def540f069317f452 (diff) | |
download | sonarqube-57ec3d37f7420a262b2724b1e7bbe958ad13b5d6.tar.gz sonarqube-57ec3d37f7420a262b2724b1e7bbe958ad13b5d6.zip |
SONAR-6740 clean shutdown of ES client
6 files changed, 76 insertions, 286 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java index 1d25678edef..deece74787a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/TomcatContexts.java @@ -83,7 +83,7 @@ public class TomcatContexts { return addContext(tomcat, contextPath, dir); } - private StandardContext addContext(Tomcat tomcat, String contextPath, File dir) { + private static StandardContext addContext(Tomcat tomcat, String contextPath, File dir) { try { StandardContext context = (StandardContext) tomcat.addWebapp(contextPath, dir.getAbsolutePath()); context.setClearReferencesHttpClientKeepAliveThread(false); diff --git a/server/sonar-server/src/main/java/org/sonar/server/es/EsClient.java b/server/sonar-server/src/main/java/org/sonar/server/es/EsClient.java index 492630efd8a..58bfe9cef4e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/es/EsClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/es/EsClient.java @@ -78,42 +78,38 @@ import org.sonar.server.search.SearchClient; public class EsClient implements Startable { public static final Logger LOGGER = Loggers.get("es"); - private final Client client; + private final SearchClient deprecatedClient; public EsClient(SearchClient deprecatedClient) { - this.client = deprecatedClient; - } - - EsClient(Client client) { - this.client = client; + this.deprecatedClient = deprecatedClient; } public RefreshRequestBuilder prepareRefresh(String... indices) { - return new ProxyRefreshRequestBuilder(client).setIndices(indices); + return new ProxyRefreshRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public FlushRequestBuilder prepareFlush(String... indices) { - return new ProxyFlushRequestBuilder(client).setIndices(indices); + return new ProxyFlushRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public IndicesStatsRequestBuilder prepareStats(String... indices) { - return new ProxyIndicesStatsRequestBuilder(client).setIndices(indices); + return new ProxyIndicesStatsRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public NodesStatsRequestBuilder prepareNodesStats(String... nodesIds) { - return new ProxyNodesStatsRequestBuilder(client).setNodesIds(nodesIds); + return new ProxyNodesStatsRequestBuilder(deprecatedClient.nativeClient()).setNodesIds(nodesIds); } public ClusterStatsRequestBuilder prepareClusterStats() { - return new ProxyClusterStatsRequestBuilder(client); + return new ProxyClusterStatsRequestBuilder(deprecatedClient.nativeClient()); } public ClusterStateRequestBuilder prepareState() { - return new ProxyClusterStateRequestBuilder(client); + return new ProxyClusterStateRequestBuilder(deprecatedClient.nativeClient()); } public ClusterHealthRequestBuilder prepareHealth(String... indices) { - return new ProxyClusterHealthRequestBuilder(client).setIndices(indices); + return new ProxyClusterHealthRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public void waitForStatus(ClusterHealthStatus status) { @@ -121,55 +117,55 @@ public class EsClient implements Startable { } public IndicesExistsRequestBuilder prepareIndicesExist(String... indices) { - return new ProxyIndicesExistsRequestBuilder(client, indices); + return new ProxyIndicesExistsRequestBuilder(deprecatedClient.nativeClient(), indices); } public CreateIndexRequestBuilder prepareCreate(String index) { - return new ProxyCreateIndexRequestBuilder(client, index); + return new ProxyCreateIndexRequestBuilder(deprecatedClient.nativeClient(), index); } public PutMappingRequestBuilder preparePutMapping(String... indices) { - return new ProxyPutMappingRequestBuilder(client).setIndices(indices); + return new ProxyPutMappingRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public SearchRequestBuilder prepareSearch(String... indices) { - return new ProxySearchRequestBuilder(client).setIndices(indices); + return new ProxySearchRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public SearchScrollRequestBuilder prepareSearchScroll(String scrollId) { - return new ProxySearchScrollRequestBuilder(scrollId, client); + return new ProxySearchScrollRequestBuilder(scrollId, deprecatedClient.nativeClient()); } public GetRequestBuilder prepareGet() { - return new ProxyGetRequestBuilder(client); + return new ProxyGetRequestBuilder(deprecatedClient.nativeClient()); } public GetRequestBuilder prepareGet(String index, String type, String id) { - return new ProxyGetRequestBuilder(client).setIndex(index).setType(type).setId(id); + return new ProxyGetRequestBuilder(deprecatedClient.nativeClient()).setIndex(index).setType(type).setId(id); } public MultiGetRequestBuilder prepareMultiGet() { - return new ProxyMultiGetRequestBuilder(client); + return new ProxyMultiGetRequestBuilder(deprecatedClient.nativeClient()); } public CountRequestBuilder prepareCount(String... indices) { - return new ProxyCountRequestBuilder(client).setIndices(indices); + return new ProxyCountRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public BulkRequestBuilder prepareBulk() { - return new ProxyBulkRequestBuilder(client); + return new ProxyBulkRequestBuilder(deprecatedClient.nativeClient()); } public DeleteRequestBuilder prepareDelete(String index, String type, String id) { - return new ProxyDeleteRequestBuilder(client, index).setType(type).setId(id); + return new ProxyDeleteRequestBuilder(deprecatedClient.nativeClient(), index).setType(type).setId(id); } public DeleteByQueryRequestBuilder prepareDeleteByQuery(String... indices) { - return new ProxyDeleteByQueryRequestBuilder(client).setIndices(indices); + return new ProxyDeleteByQueryRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public IndexRequestBuilder prepareIndex(String index, String type) { - return new ProxyIndexRequestBuilder(client).setIndex(index).setType(type); + return new ProxyIndexRequestBuilder(deprecatedClient.nativeClient()).setIndex(index).setType(type); } public OptimizeRequestBuilder prepareOptimize(String indexName) { @@ -179,7 +175,7 @@ public class EsClient implements Startable { } public ClearIndicesCacheRequestBuilder prepareClearCache(String... indices) { - return new ProxyClearCacheRequestBuilder(client).setIndices(indices); + return new ProxyClearCacheRequestBuilder(deprecatedClient.nativeClient()).setIndices(indices); } public long getMaxFieldValue(String indexName, String typeName, String fieldName) { @@ -205,6 +201,6 @@ public class EsClient implements Startable { } protected Client nativeClient() { - return client; + return deprecatedClient.nativeClient(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/SearchClient.java b/server/sonar-server/src/main/java/org/sonar/server/search/SearchClient.java index c0dd91ef16b..24ea238ce4a 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/SearchClient.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/SearchClient.java @@ -19,6 +19,7 @@ */ package org.sonar.server.search; +import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang.StringUtils; import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder; import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequestBuilder; @@ -26,25 +27,13 @@ import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequestBuild import org.elasticsearch.action.admin.indices.refresh.RefreshRequestBuilder; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.count.CountRequestBuilder; -import org.elasticsearch.action.delete.DeleteRequestBuilder; import org.elasticsearch.action.deletebyquery.DeleteByQueryRequestBuilder; -import org.elasticsearch.action.explain.ExplainRequestBuilder; import org.elasticsearch.action.get.GetRequestBuilder; import org.elasticsearch.action.get.MultiGetRequestBuilder; -import org.elasticsearch.action.index.IndexRequestBuilder; -import org.elasticsearch.action.mlt.MoreLikeThisRequestBuilder; -import org.elasticsearch.action.percolate.MultiPercolateRequestBuilder; -import org.elasticsearch.action.percolate.PercolateRequestBuilder; -import org.elasticsearch.action.search.ClearScrollRequestBuilder; -import org.elasticsearch.action.search.MultiSearchRequestBuilder; import org.elasticsearch.action.search.SearchRequestBuilder; import org.elasticsearch.action.search.SearchScrollRequestBuilder; -import org.elasticsearch.action.suggest.SuggestRequestBuilder; -import org.elasticsearch.action.termvector.MultiTermVectorsRequestBuilder; -import org.elasticsearch.action.termvector.TermVectorRequestBuilder; -import org.elasticsearch.action.update.UpdateRequestBuilder; +import org.elasticsearch.client.Client; import org.elasticsearch.client.transport.TransportClient; -import org.elasticsearch.common.Nullable; import org.elasticsearch.common.logging.ESLoggerFactory; import org.elasticsearch.common.logging.slf4j.Slf4jESLoggerFactory; import org.elasticsearch.common.settings.ImmutableSettings; @@ -56,7 +45,6 @@ import org.sonar.process.ProcessProperties; import org.sonar.server.es.request.ProxyBulkRequestBuilder; import org.sonar.server.es.request.ProxyCountRequestBuilder; import org.sonar.server.es.request.ProxyCreateIndexRequestBuilder; -import org.sonar.server.es.request.ProxyDeleteRequestBuilder; import org.sonar.server.es.request.ProxyGetRequestBuilder; import org.sonar.server.es.request.ProxyIndicesExistsRequestBuilder; import org.sonar.server.es.request.ProxyMultiGetRequestBuilder; @@ -68,175 +56,94 @@ import org.sonar.server.es.request.ProxySearchScrollRequestBuilder; /** * ElasticSearch Node used to connect to index. */ -public class SearchClient extends TransportClient implements Startable { +public class SearchClient implements Startable { + + private final Settings settings; + private Client nativeClient; public SearchClient(Settings settings) { - super(ImmutableSettings.settingsBuilder() - .put("node.name", StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.CLUSTER_NODE_NAME), "sq_local_client")) - .put("network.bind_host", StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.SEARCH_HOST), "localhost")) - .put("node.rack_id", StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.CLUSTER_NODE_NAME), "unknown")) - .put("cluster.name", StringUtils.defaultIfBlank(settings.getString(ProcessProperties.CLUSTER_NAME), "sonarqube")) - .build()); - initLogging(); - this.addTransportAddress(new InetSocketTransportAddress(StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.SEARCH_HOST), LoopbackAddress.get().getHostAddress()), - settings.getInt(ProcessProperties.SEARCH_PORT))); + this.settings = settings; + } + + @VisibleForTesting + public SearchClient(Settings settings, Client nativeClient) { + this.settings = settings; + this.nativeClient = nativeClient; } - private void initLogging() { - ESLoggerFactory.setDefaultFactory(new Slf4jESLoggerFactory()); + public Client nativeClient() { + if (nativeClient == null) { + throw new IllegalStateException(); + } + return nativeClient; } public RefreshRequestBuilder prepareRefresh(String... indices) { - return new ProxyRefreshRequestBuilder(this).setIndices(indices); + return new ProxyRefreshRequestBuilder(nativeClient).setIndices(indices); } public IndicesExistsRequestBuilder prepareIndicesExist(String... indices) { - return new ProxyIndicesExistsRequestBuilder(this, indices); + return new ProxyIndicesExistsRequestBuilder(nativeClient, indices); } public CreateIndexRequestBuilder prepareCreate(String index) { - return new ProxyCreateIndexRequestBuilder(this, index); + return new ProxyCreateIndexRequestBuilder(nativeClient, index); } public PutMappingRequestBuilder preparePutMapping(String... indices) { - return new ProxyPutMappingRequestBuilder(this).setIndices(indices); + return new ProxyPutMappingRequestBuilder(nativeClient).setIndices(indices); } - @Override public SearchRequestBuilder prepareSearch(String... indices) { - return new ProxySearchRequestBuilder(this).setIndices(indices); + return new ProxySearchRequestBuilder(nativeClient).setIndices(indices); } - @Override public SearchScrollRequestBuilder prepareSearchScroll(String scrollId) { - return new ProxySearchScrollRequestBuilder(scrollId, this); + return new ProxySearchScrollRequestBuilder(scrollId, nativeClient); } - @Override public GetRequestBuilder prepareGet() { - return new ProxyGetRequestBuilder(this); + return new ProxyGetRequestBuilder(nativeClient); } - @Override public MultiGetRequestBuilder prepareMultiGet() { - return new ProxyMultiGetRequestBuilder(this); + return new ProxyMultiGetRequestBuilder(nativeClient); } - @Override public CountRequestBuilder prepareCount(String... indices) { - return new ProxyCountRequestBuilder(this).setIndices(indices); + return new ProxyCountRequestBuilder(nativeClient).setIndices(indices); } - @Override public BulkRequestBuilder prepareBulk() { - return new ProxyBulkRequestBuilder(this); + return new ProxyBulkRequestBuilder(nativeClient); } - @Override public DeleteByQueryRequestBuilder prepareDeleteByQuery(String... indices) { throw new UnsupportedOperationException("Delete by query must not be used. See https://github.com/elastic/elasticsearch/issues/10067. See alternatives in BulkIndexer."); } - // **************************************************************************************************************** - // Not yet implemented methods - // **************************************************************************************************************** - - @Override - public GetRequestBuilder prepareGet(String index, String type, String id) { - throw throwNotYetImplemented(); - } - - @Override - public IndexRequestBuilder prepareIndex() { - throw throwNotYetImplemented(); - } - - @Override - public IndexRequestBuilder prepareIndex(String index, String type) { - throw throwNotYetImplemented(); - } - - @Override - public IndexRequestBuilder prepareIndex(String index, String type, @Nullable String id) { - throw throwNotYetImplemented(); - } - - @Override - public MultiSearchRequestBuilder prepareMultiSearch() { - throw throwNotYetImplemented(); - } - - @Override - public UpdateRequestBuilder prepareUpdate() { - throw throwNotYetImplemented(); - } - - @Override - public UpdateRequestBuilder prepareUpdate(String index, String type, String id) { - throw throwNotYetImplemented(); - } - - @Override - public DeleteRequestBuilder prepareDelete() { - throw throwNotYetImplemented(); - } - - @Override - public DeleteRequestBuilder prepareDelete(String index, String type, String id) { - return new ProxyDeleteRequestBuilder(this, index).setType(type).setId(id); - } - - @Override - public PercolateRequestBuilder preparePercolate() { - throw throwNotYetImplemented(); - } - - @Override - public MultiPercolateRequestBuilder prepareMultiPercolate() { - throw throwNotYetImplemented(); - } - - @Override - public SuggestRequestBuilder prepareSuggest(String... indices) { - throw throwNotYetImplemented(); - } - - @Override - public MoreLikeThisRequestBuilder prepareMoreLikeThis(String index, String type, String id) { - throw throwNotYetImplemented(); - } - - @Override - public TermVectorRequestBuilder prepareTermVector(String index, String type, String id) { - throw throwNotYetImplemented(); - } - - @Override - public MultiTermVectorsRequestBuilder prepareMultiTermVectors() { - throw throwNotYetImplemented(); - } - - @Override - public ExplainRequestBuilder prepareExplain(String index, String type, String id) { - throw throwNotYetImplemented(); - } - - @Override - public ClearScrollRequestBuilder prepareClearScroll() { - throw throwNotYetImplemented(); - } - - private static IllegalStateException throwNotYetImplemented() { - return new IllegalStateException("Not yet implemented"); - } - @Override - public void start() { - // nothing to do + public synchronized void start() { + if (nativeClient == null) { + ESLoggerFactory.setDefaultFactory(new Slf4jESLoggerFactory()); + org.elasticsearch.common.settings.Settings esSettings = ImmutableSettings.settingsBuilder() + .put("node.name", StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.CLUSTER_NODE_NAME), "sq_local_client")) + .put("network.bind_host", StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.SEARCH_HOST), "localhost")) + .put("node.rack_id", StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.CLUSTER_NODE_NAME), "unknown")) + .put("cluster.name", StringUtils.defaultIfBlank(settings.getString(ProcessProperties.CLUSTER_NAME), "sonarqube")) + .build(); + nativeClient = new TransportClient(esSettings); + ((TransportClient) nativeClient).addTransportAddress(new InetSocketTransportAddress(StringUtils.defaultIfEmpty(settings.getString(ProcessProperties.SEARCH_HOST), + LoopbackAddress.get() + .getHostAddress()), + settings.getInt(ProcessProperties.SEARCH_PORT))); + } } @Override public void stop() { - close(); + if (nativeClient != null) { + nativeClient.close(); + } } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java index 51b2e24e120..02b9cdce9d2 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java @@ -44,6 +44,7 @@ import org.junit.rules.ExternalResource; import org.sonar.api.config.Settings; import org.sonar.core.platform.ComponentContainer; import org.sonar.server.search.BaseDoc; +import org.sonar.server.search.SearchClient; import org.sonar.test.TestUtils; import java.io.File; @@ -97,7 +98,7 @@ public class EsTester extends ExternalResource { DeleteIndexResponse response = node.client().admin().indices().prepareDelete("_all").get(); assertThat(response.isAcknowledged()).isTrue(); - client = new EsClient(node.client()); + client = new EsClient(new SearchClient(new Settings(), node.client())); client.start(); if (!definitions.isEmpty()) { diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java index 23ac6629572..5a50c6f5fc8 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java @@ -52,6 +52,7 @@ public class BaseIndexTest { settings.setProperty(ProcessProperties.SEARCH_PORT, String.valueOf(holder.getPort())); settings.setProperty(ProcessProperties.SEARCH_HOST, String.valueOf(holder.getHostName())); searchClient = new SearchClient(settings); + searchClient.start(); } @After @@ -71,7 +72,7 @@ public class BaseIndexTest { public void creates_domain_index() { BaseIndex index = getIndex(this.searchClient); - IndicesExistsResponse indexExistsResponse = index.getClient().admin().indices() + IndicesExistsResponse indexExistsResponse = index.getClient().nativeClient().admin().indices() .prepareExists(IndexDefinition.TEST.getIndexName()).execute().actionGet(); assertThat(indexExistsResponse.isExists()).isTrue(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/SearchClientMediumTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/SearchClientMediumTest.java index c9f019ce17e..d294aabbf21 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/search/SearchClientMediumTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/search/SearchClientMediumTest.java @@ -33,6 +33,7 @@ public class SearchClientMediumTest { @ClassRule public static ServerTester tester = new ServerTester(); + @Rule public UserSessionRule userSessionRule = UserSessionRule.forServerTester(tester); @@ -45,43 +46,6 @@ public class SearchClientMediumTest { } @Test - public void prepare_multi_search_is_not_yet_implemented() { - try { - searchClient.prepareMultiSearch(); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_update_is_not_yet_implemented() { - try { - searchClient.prepareUpdate(); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - - try { - searchClient.prepareUpdate("index", "type", "id"); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_delete_is_not_yet_implemented() { - try { - searchClient.prepareDelete(); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test public void delete_by_query_is_not_supported() { try { searchClient.prepareDeleteByQuery(); @@ -91,83 +55,4 @@ public class SearchClientMediumTest { } } - @Test - public void prepare_percolate_is_not_yet_implemented() { - try { - searchClient.preparePercolate(); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_multi_percolate_is_not_yet_implemented() { - try { - searchClient.prepareMultiPercolate(); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_suggest_is_not_yet_implemented() { - try { - searchClient.prepareSuggest("index"); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_more_like_this_is_not_yet_implemented() { - try { - searchClient.prepareMoreLikeThis("index", "tpye", "id"); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_term_vector_is_not_yet_implemented() { - try { - searchClient.prepareTermVector("index", "tpye", "id"); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_multi_term_vectors_is_not_yet_implemented() { - try { - searchClient.prepareMultiTermVectors(); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_explain_is_not_yet_implemented() { - try { - searchClient.prepareExplain("index", "tpye", "id"); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } - - @Test - public void prepare_clear_scroll_is_not_yet_implemented() { - try { - searchClient.prepareClearScroll(); - fail(); - } catch (Exception e) { - assertThat(e).isInstanceOf(IllegalStateException.class).hasMessage("Not yet implemented"); - } - } } |