diff --git a/config/alfresco/subsystems/Search/solr4/solr-search-context.xml b/config/alfresco/subsystems/Search/solr4/solr-search-context.xml index 64b16fd590..049e1d0d9e 100644 --- a/config/alfresco/subsystems/Search/solr4/solr-search-context.xml +++ b/config/alfresco/subsystems/Search/solr4/solr-search-context.xml @@ -90,6 +90,15 @@ SpacesStore + + ${solr4.alfresco.nodeString} + + + ${solr4.alfresco.numShards} + + + ${solr4.alfresco.replicationFactor} + @@ -107,6 +116,15 @@ SpacesStore + + ${solr4.archive.nodeString} + + + ${solr4.archive.numShards} + + + ${solr4.archive.replicationFactor} + diff --git a/config/alfresco/subsystems/Search/solr4/solr-search.properties b/config/alfresco/subsystems/Search/solr4/solr-search.properties index 9c9a799611..857f822afb 100644 --- a/config/alfresco/subsystems/Search/solr4/solr-search.properties +++ b/config/alfresco/subsystems/Search/solr4/solr-search.properties @@ -7,4 +7,15 @@ solr.baseUrl=/solr4 # # Solr Suggester properties # -solr.suggester.enabled=true \ No newline at end of file +solr.suggester.enabled=true +# +# Default unsharded + +solr4.alfresco.nodeString= +solr4.alfresco.numShards=1 +solr4.alfresco.replicationFactor=1 + +solr4.archive.nodeString= +solr4.archive.numShards=1 +solr4.archive.replicationFactor=1 + \ No newline at end of file diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 9d62c0f20a..9e76664358 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2015 Alfresco Software Limited. * * This file is part of Alfresco * @@ -59,6 +59,7 @@ import org.alfresco.service.cmr.search.SearchParameters.SortDefinition; import org.alfresco.service.cmr.search.StatsParameters; import org.alfresco.service.cmr.security.AuthorityType; import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.util.Pair; import org.alfresco.util.PropertyCheck; import org.apache.commons.codec.net.URLCodec; import org.apache.commons.httpclient.Header; @@ -104,9 +105,7 @@ public class SolrQueryHTTPClient implements BeanFactoryAware private List storeMappings; - private HashMap httpClients = new HashMap(); - - private HashMap> mappingLookup = new HashMap>(); + private HashMap mappingLookup = new HashMap(); private String alternativeDictionary = CMISStrictDictionaryService.DEFAULT; @@ -137,21 +136,7 @@ public class SolrQueryHTTPClient implements BeanFactoryAware for(SolrStoreMapping mapping : storeMappings) { - List storeMappingList = mappingLookup.get(mapping.getStoreRef()); - if(storeMappingList == null) - { - storeMappingList = new ArrayList(50); - mappingLookup.put(mapping.getStoreRef(), storeMappingList); - - HttpClientFactory httpClientFactory = (HttpClientFactory)beanFactory.getBean(mapping.getHttpClientFactory()); - HttpClient httpClient = httpClientFactory.getHttpClient(); - HttpClientParams params = httpClient.getParams(); - params.setBooleanParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true); - httpClient.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); - httpClients.put(mapping.getStoreRef(), httpClient); - } - - storeMappingList.add(mapping); + mappingLookup.put(mapping.getStoreRef(), new SolrStoreMappingWrapper(mapping, beanFactory)); } } @@ -245,13 +230,20 @@ public class SolrQueryHTTPClient implements BeanFactoryAware try { StoreRef store = extractStoreRef(searchParameters); - List mappings = extractMappings(store); + SolrStoreMappingWrapper mapping = extractMapping(store); Locale locale = extractLocale(searchParameters); - String url = buildStatsUrl(searchParameters, mappings.get(0).getBaseUrl(), locale); + Pair httpClientAndBaseUrl = mapping.getHttpClientAndBaseUrl(); + HttpClient httpClient = httpClientAndBaseUrl.getFirst(); + String url = buildStatsUrl(searchParameters, httpClientAndBaseUrl.getSecond(), locale); JSONObject body = buildStatsBody(searchParameters, tenantService.getCurrentUserDomain(), locale); - return (SolrStatsResult) postSolrQuery(store, url, body, new SolrJsonProcessor() { + if(httpClient == null) + { + throw new AlfrescoRuntimeException("No http client for store " + store.toString()); + } + + return (SolrStatsResult) postSolrQuery(httpClient, url, body, new SolrJsonProcessor() { @Override public SolrStatsResult getResult(JSONObject json) @@ -335,12 +327,16 @@ public class SolrQueryHTTPClient implements BeanFactoryAware try { StoreRef store = extractStoreRef(searchParameters); - List mappings = extractMappings(store); + SolrStoreMappingWrapper mapping = extractMapping(store); Locale locale = extractLocale(searchParameters); URLCodec encoder = new URLCodec(); StringBuilder url = new StringBuilder(); - url.append(mappings.get(0).getBaseUrl()); + + Pair httpClientAndBaseUrl = mapping.getHttpClientAndBaseUrl(); + HttpClient httpClient = httpClientAndBaseUrl.getFirst(); + + url.append(httpClientAndBaseUrl.getSecond()); String languageUrlFragment = extractLanguageFragment(language); url.append("/").append(languageUrlFragment); @@ -351,29 +347,25 @@ public class SolrQueryHTTPClient implements BeanFactoryAware url.append("?wt=").append(encoder.encode("json", "UTF-8")); url.append("&fl=").append(encoder.encode("DBID,score", "UTF-8")); - if((searchParameters.getStores().size() > 1) || (mappings.size() > 1)) + if((searchParameters.getStores().size() > 1) || (mapping.isSharded())) { boolean requiresSeparator = false; url.append("&shards="); for(StoreRef storeRef : searchParameters.getStores()) { - for(SolrStoreMapping shard : extractMappings(storeRef)) + SolrStoreMappingWrapper storeMapping = extractMapping(storeRef); + + if(requiresSeparator) { - if(requiresSeparator) - { - url.append(','); - } - else - { - requiresSeparator = true; - } - - HttpClientFactory httpClientFactory = (HttpClientFactory)beanFactory.getBean(shard.getHttpClientFactory()); - url.append(httpClientFactory.getHost()); - url.append(':'); - url.append(httpClientFactory.getPort()); - url.append(encoder.encode(shard.getBaseUrl(), "UTF-8")); + url.append(','); } + else + { + requiresSeparator = true; + } + + url.append(storeMapping.getShards()); + } } @@ -551,7 +543,10 @@ public class SolrQueryHTTPClient implements BeanFactoryAware body.put("textAttributes", textAttributes); final int maximumResults = maxResults; //just needed for the final parameter - return (ResultSet) postSolrQuery(store, url.toString(), body, new SolrJsonProcessor() { + + + + return (ResultSet) postSolrQuery(httpClient, url.toString(), body, new SolrJsonProcessor() { @Override public SolrJSONResultSet getResult(JSONObject json) @@ -579,24 +574,24 @@ public class SolrQueryHTTPClient implements BeanFactoryAware } } - protected JSONResult postSolrQuery(StoreRef store, String url, JSONObject body, SolrJsonProcessor jsonProcessor) + protected JSONResult postSolrQuery(HttpClient httpClient, String url, JSONObject body, SolrJsonProcessor jsonProcessor) throws UnsupportedEncodingException, IOException, HttpException, URIException, JSONException { - return postSolrQuery(store, url, body, jsonProcessor, null); + return postSolrQuery(httpClient, url, body, jsonProcessor, null); } - protected JSONResult postSolrQuery(StoreRef store, String url, JSONObject body, SolrJsonProcessor jsonProcessor, String spellCheckParams) + protected JSONResult postSolrQuery(HttpClient httpClient, String url, JSONObject body, SolrJsonProcessor jsonProcessor, String spellCheckParams) throws UnsupportedEncodingException, IOException, HttpException, URIException, JSONException { - JSONObject json = postQuery(store, url, body); + JSONObject json = postQuery(httpClient, url, body); if (spellCheckParams != null) { SpellCheckDecisionManager manager = new SpellCheckDecisionManager(json, url, body, spellCheckParams); if (manager.isCollate()) { - json = postQuery(store, manager.getUrl(), body); + json = postQuery(httpClient, manager.getUrl(), body); } json.put("spellcheck", manager.getSpellCheckJsonValue()); } @@ -613,7 +608,7 @@ public class SolrQueryHTTPClient implements BeanFactoryAware return results; } - protected JSONObject postQuery(StoreRef store, String url, JSONObject body) throws UnsupportedEncodingException, + protected JSONObject postQuery(HttpClient httpClient, String url, JSONObject body) throws UnsupportedEncodingException, IOException, HttpException, URIException, JSONException { PostMethod post = new PostMethod(url); @@ -625,13 +620,6 @@ public class SolrQueryHTTPClient implements BeanFactoryAware try { - HttpClient httpClient = httpClients.get(store); - - if(httpClient == null) - { - throw new AlfrescoRuntimeException("No http client for store " + store.toString()); - } - httpClient.executeMethod(post); if(post.getStatusCode() == HttpStatus.SC_MOVED_PERMANENTLY || post.getStatusCode() == HttpStatus.SC_MOVED_TEMPORARILY) @@ -719,11 +707,11 @@ public class SolrQueryHTTPClient implements BeanFactoryAware return languageUrlFragment; } - private List extractMappings(StoreRef store) + private SolrStoreMappingWrapper extractMapping(StoreRef store) { - List mappings = mappingLookup.get(store); + SolrStoreMappingWrapper mappings = mappingLookup.get(store); - if ((mappings == null) || (mappings.size() == 0)) + if (mappings == null) { throw new AlfrescoRuntimeException("No solr query support for store " + store); } diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMapping.java b/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMapping.java index 911408afaa..a5795e25df 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMapping.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2015 Alfresco Software Limited. * * This file is part of Alfresco * @@ -18,10 +18,10 @@ */ package org.alfresco.repo.search.impl.solr; +import java.util.LinkedHashSet; + import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.util.PropertyCheck; import org.springframework.beans.factory.BeanNameAware; -import org.springframework.beans.factory.InitializingBean; /** * @author Andy @@ -41,18 +41,24 @@ public class SolrStoreMapping implements BeanNameAware private String beanName; + private String[] nodes = new String[0]; + + private int numShards = 1; + + private int replicationFactor = 1; + public SolrStoreMapping() { } - public SolrStoreMapping(String protocol, String identifier, String httpClientFactory, String baseUrl) - { - this.protocol = protocol; - this.identifier = identifier; - this.httpClientFactory = httpClientFactory; - this.baseUrl = baseUrl; - } +// public SolrStoreMapping(String protocol, String identifier, String httpClientFactory, String baseUrl) +// { +// this.protocol = protocol; +// this.identifier = identifier; +// this.httpClientFactory = httpClientFactory; +// this.baseUrl = baseUrl; +// } /** * @return the storeRef @@ -146,5 +152,94 @@ public class SolrStoreMapping implements BeanNameAware } } + /** + * @return the nodes + */ + public String getNodeString() + { + StringBuilder builder = new StringBuilder(); + for(String node : nodes) + { + if(builder.length() > 0) + { + builder.append(','); + } + builder.append(node); + } + return builder.toString(); + } + /** + * @return the nodes + */ + public String[] getNodes() + { + return nodes; + } + + /** + * @param nodes + * the nodes to set + */ +// public void setNodes(String[] nodes) +// { +// LinkedHashSet unique = new LinkedHashSet(); +// for(String node : nodes) +// { +// for(String split : node.split(",")) +// { +// unique.add(split.trim()); +// } +// } +// +// this.nodes = unique.toArray(new String[0]); +// } + + public void setNodeString(String nodes) + { + LinkedHashSet unique = new LinkedHashSet(); + + for(String split : nodes.split(",")) + { + unique.add(split.trim()); + } + + + this.nodes = unique.toArray(new String[0]); + } + + /** + * @return the numShards + */ + public int getNumShards() + { + return numShards; + } + + /** + * @param numShards + * the numShards to set + */ + public void setNumShards(int numShards) + { + this.numShards = numShards; + } + + /** + * @return the replicationFactor + */ + public int getReplicationFactor() + { + return replicationFactor; + } + + /** + * @param replicationFactor + * the replicationFactor to set + */ + public void setReplicationFactor(int replicationFactor) + { + this.replicationFactor = replicationFactor; + } + } diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMappingWrapper.java b/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMappingWrapper.java new file mode 100644 index 0000000000..ea71b69a0a --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrStoreMappingWrapper.java @@ -0,0 +1,404 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.search.impl.solr; + +import java.io.UnsupportedEncodingException; +import java.util.HashSet; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.httpclient.HttpClientFactory; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParserException; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.Pair; +import org.alfresco.util.shard.ExplicitShardingPolicy; +import org.apache.commons.codec.net.URLCodec; +import org.apache.commons.httpclient.HttpClient; +import org.apache.commons.httpclient.params.HttpClientParams; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; +import org.springframework.beans.factory.BeanFactoryAware; +import org.springframework.beans.factory.BeanNameAware; + +/** + * @author Andy + */ +public class SolrStoreMappingWrapper +{ + + private HttpClientFactory httpClientFactory; + + private LinkedHashSet httpClientsAndBaseURLs = new LinkedHashSet(); + + private ExplicitShardingPolicy policy; + + private Random random; + + private BeanFactory beanFactory; + + private SolrStoreMapping wrapped; + + public SolrStoreMappingWrapper(SolrStoreMapping wrapped, BeanFactory beanFactory) + { + this.wrapped = wrapped; + this.beanFactory = beanFactory; + init(); + } + + public void init() + { + httpClientFactory = (HttpClientFactory)beanFactory.getBean(wrapped.getHttpClientFactory()); + random = new Random(123); + + if ((wrapped.getNodes() == null) || (wrapped.getNodes().length == 0)) + { + HttpClient httpClient = httpClientFactory.getHttpClient(); + HttpClientParams params = httpClient.getParams(); + //params.setBooleanParameter(HttpClientParams.PREEMPTIVE_AUTHENTICATION, true); + //httpClient.getState().setCredentials(new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT), new UsernamePasswordCredentials("admin", "admin")); + httpClientsAndBaseURLs.add(new HttpClientAndBaseUrl(httpClient, wrapped.getBaseUrl())); + } + else + { + for (String node : wrapped.getNodes()) + { + String nodeHost = httpClientFactory.getHost(); + String nodePort = "" + httpClientFactory.getPort(); + String nodeBaseUrl = wrapped.getBaseUrl(); + + if (node.length() > 0) + { + int colon = node.indexOf(':'); + int forward = (colon > -1) ? node.indexOf('/', colon) : node.indexOf('/'); + + if (colon == -1) + { + if (forward == -1) + { + // single value + if (node.startsWith("/")) + { + nodeBaseUrl = node; + } + try + { + int port = Integer.parseInt(node); + nodePort = "" + port; + } + catch (NumberFormatException nfe) + { + nodeHost = node; + } + } + else + { + try + { + String potentialPort = node.substring(0, forward); + if (potentialPort.length() > 0) + { + int port = Integer.parseInt(potentialPort); + nodePort = "" + port; + } + } + catch (NumberFormatException nfe) + { + nodeHost = node.substring(0, forward); + } + nodeBaseUrl = node.substring(forward); + } + } + else + { + if (forward == -1) + { + if (colon > 0) + { + nodeHost = node.substring(0, colon); + } + if (colon + 1 < node.length()) + { + String port = node.substring(colon + 1); + if (port.length() > 0) + { + nodePort = port; + } + } + } + else + { + if (colon > 0) + { + nodeHost = node.substring(0, colon); + } + + String port = node.substring(colon + 1, forward); + if (port.length() > 0) + { + nodePort = port; + } + nodeBaseUrl = node.substring(forward); + + } + } + } + + try + { + int realPort = Integer.parseInt(nodePort); + httpClientsAndBaseURLs.add(new HttpClientAndBaseUrl(httpClientFactory.getHttpClient(nodeHost, realPort), nodeBaseUrl)); + } + catch (NumberFormatException nfe) + { + httpClientsAndBaseURLs.add(new HttpClientAndBaseUrl(httpClientFactory.getHttpClient(nodeHost, httpClientFactory.getPort()), nodeBaseUrl)); + } + } + } + + policy = new ExplicitShardingPolicy(wrapped.getNumShards(), wrapped.getReplicationFactor(), httpClientsAndBaseURLs.size()); + + } + + + + + public boolean isSharded() + { + return wrapped.getNumShards() > 1; + } + + public String getShards() + { + + if (!policy.configurationIsValid()) + { + throw new AlfrescoRuntimeException("Invalid shard configuration: shard = " + + wrapped.getNumShards() + " reoplicationFactor = " + wrapped.getReplicationFactor() + " with node count = " + httpClientsAndBaseURLs.size()); + } + + return getShards2(); + } + + private String getShards1() + { + try + { + URLCodec encoder = new URLCodec(); + StringBuilder builder = new StringBuilder(); + + Set shards = new HashSet(); + for (int i = 0; i < httpClientsAndBaseURLs.size(); i += wrapped.getReplicationFactor()) + { + for (Integer shardId : policy.getShardIdsForNode(i + 1)) + { + if (!shards.contains(shardId % wrapped.getNumShards())) + { + if (shards.size() > 0) + { + builder.append(','); + } + HttpClientAndBaseUrl httpClientAndBaseUrl = httpClientsAndBaseURLs.toArray(new HttpClientAndBaseUrl[0])[i]; + builder.append(encoder.encode(httpClientAndBaseUrl.getHost(), "UTF-8")); + builder.append(':'); + builder.append(encoder.encode("" + httpClientAndBaseUrl.getPort(), "UTF-8")); + if (httpClientAndBaseUrl.getBaseUrl().startsWith("/")) + { + builder.append(encoder.encode(httpClientAndBaseUrl.getBaseUrl(), "UTF-8")); + } + else + { + builder.append(encoder.encode("/" + httpClientAndBaseUrl.getBaseUrl(), "UTF-8")); + } + + builder.append('-').append(shardId); + + shards.add(shardId % wrapped.getNumShards()); + } + + } + } + return builder.toString(); + } + catch (UnsupportedEncodingException e) + { + throw new LuceneQueryParserException("", e); + } + } + + private String getShards2() + { + try + { + URLCodec encoder = new URLCodec(); + StringBuilder builder = new StringBuilder(); + + for (int shard = 0; shard < wrapped.getNumShards(); shard++) + { + int position = random.nextInt(wrapped.getReplicationFactor()); + List nodeInstances = policy.getNodeInstancesForShardId(shard); + Integer nodeId = nodeInstances.get(position); + + if (builder.length() > 0) + { + builder.append(','); + } + HttpClientAndBaseUrl httpClientAndBaseUrl = httpClientsAndBaseURLs.toArray(new HttpClientAndBaseUrl[0])[nodeId-1]; + builder.append(encoder.encode(httpClientAndBaseUrl.getHost(), "UTF-8")); + builder.append(':'); + builder.append(encoder.encode("" + httpClientAndBaseUrl.getPort(), "UTF-8")); + if (httpClientAndBaseUrl.getBaseUrl().startsWith("/")) + { + builder.append(encoder.encode(httpClientAndBaseUrl.getBaseUrl(), "UTF-8")); + } + else + { + builder.append(encoder.encode("/" + httpClientAndBaseUrl.getBaseUrl(), "UTF-8")); + } + + builder.append('-').append(shard); + + } + return builder.toString(); + } + catch (UnsupportedEncodingException e) + { + throw new LuceneQueryParserException("", e); + } + } + + /** + * @return + */ + public int getNodeCount() + { + return httpClientsAndBaseURLs.size(); + } + + private static class HttpClientAndBaseUrl + { + + private HttpClient httpClient; + + private String baseUrl; + + HttpClientAndBaseUrl(HttpClient httpClient, String baseUrl) + { + this.httpClient = httpClient; + this.baseUrl = baseUrl; + } + + public String getBaseUrl() + { + return baseUrl; + } + + public String getHost() + { + return httpClient.getHostConfiguration().getHost(); + } + + public int getPort() + { + return httpClient.getHostConfiguration().getPort(); + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((baseUrl == null) ? 0 : baseUrl.hashCode()); + result = prime * result + ((getHost() == null) ? 0 : getHost().hashCode()); + result = prime * result + getPort(); + return result; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + HttpClientAndBaseUrl other = (HttpClientAndBaseUrl) obj; + if (baseUrl == null) + { + if (other.baseUrl != null) + return false; + } + else if (!baseUrl.equals(other.baseUrl)) + return false; + if (httpClient == null) + { + if (other.httpClient != null) + return false; + } + else if (!getHost().equals(other.getHost())) + return false; + else if (getPort() != other.getPort()) + return false; + return true; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() + { + return "HttpClientAndBaseUrl [getBaseUrl()=" + getBaseUrl() + ", getHost()=" + getHost() + ", getPort()=" + getPort() + "]"; + } + + } + + /** + * @return + */ + public Pair getHttpClientAndBaseUrl() + { + + if (!policy.configurationIsValid()) + { + throw new AlfrescoRuntimeException("Invalid shard configuration: shard = " + + wrapped.getNumShards() + " reoplicationFactor = " + wrapped.getReplicationFactor() + " with node count = " + httpClientsAndBaseURLs.size()); + } + + int shard = random.nextInt(wrapped.getNumShards()); + int position = random.nextInt(wrapped.getReplicationFactor()); + List nodeInstances = policy.getNodeInstancesForShardId(shard); + Integer nodeId = nodeInstances.get(position); + HttpClientAndBaseUrl httpClientAndBaseUrl = httpClientsAndBaseURLs.toArray(new HttpClientAndBaseUrl[0])[nodeId-1]; + return new Pair<>(httpClientAndBaseUrl.httpClient, isSharded() ? httpClientAndBaseUrl.baseUrl+"-"+shard : httpClientAndBaseUrl.baseUrl); + } + +} diff --git a/source/test-java/org/alfresco/AllUnitTestsSuite.java b/source/test-java/org/alfresco/AllUnitTestsSuite.java index 291a346aa5..9da4397a3b 100644 --- a/source/test-java/org/alfresco/AllUnitTestsSuite.java +++ b/source/test-java/org/alfresco/AllUnitTestsSuite.java @@ -100,5 +100,6 @@ public class AllUnitTestsSuite extends TestSuite suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.facet.SolrFacetComparatorTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.BeanExtenderUnitTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SpellCheckDecisionManagerTest.class)); + suite.addTest(new JUnit4TestAdapter(org.alfresco.repo.search.impl.solr.SolrStoreMappingWrapperTest.class)); } } diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/SolrStoreMappingWrapperTest.java b/source/test-java/org/alfresco/repo/search/impl/solr/SolrStoreMappingWrapperTest.java new file mode 100644 index 0000000000..ed600c7a15 --- /dev/null +++ b/source/test-java/org/alfresco/repo/search/impl/solr/SolrStoreMappingWrapperTest.java @@ -0,0 +1,252 @@ +/* + * Copyright (C) 2005-2015 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.search.impl.solr; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doReturn; + +import org.alfresco.httpclient.HttpClientFactory; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.util.Pair; +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpClient; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; +import org.springframework.beans.factory.BeanFactory; + +/** + * @author Andy + * + */ + +@RunWith(MockitoJUnitRunner.class) +public class SolrStoreMappingWrapperTest +{ + SolrStoreMapping mapping; + + SolrStoreMappingWrapper wrapper; + + @Mock + HttpClientFactory httpClientFactory; + + @Mock + HttpClient httpClientCommon; + + @Mock + HttpClient httpClient1; + + @Mock + HttpClient httpClient2; + + @Mock + HttpClient httpClient3; + + @Mock + HttpClient httpClient4; + + @Mock + HttpClient httpClient5; + + @Mock + HttpClient httpClient6; + + @Mock + HttpClient httpClient7; + + @Mock + HttpClient httpClient8; + + @Mock + HttpClient httpClient9; + + @Mock + HostConfiguration hostConfigurationCommon; + + @Mock + HostConfiguration hostConfiguration1; + + @Mock + HostConfiguration hostConfiguration2; + + @Mock + HostConfiguration hostConfiguration3; + + @Mock + HostConfiguration hostConfiguration4; + + @Mock + HostConfiguration hostConfiguration5; + + @Mock + HostConfiguration hostConfiguration6; + + @Mock + HostConfiguration hostConfiguration7; + + @Mock + HostConfiguration hostConfiguration8; + + @Mock + HostConfiguration hostConfiguration9; + + private SolrStoreMapping unsharded; + + private SolrStoreMappingWrapper unshardedWrapper; + + @Mock + private BeanFactory beanFactory; + + + + + @Before + public void init() + { + doReturn("common").when(hostConfigurationCommon).getHost(); + doReturn(999).when(hostConfigurationCommon).getPort(); + + doReturn("host").when(hostConfiguration1).getHost(); + doReturn(999).when(hostConfiguration1).getPort(); + + doReturn("common").when(hostConfiguration2).getHost(); + doReturn(123).when(hostConfiguration2).getPort(); + + doReturn("port").when(hostConfiguration3).getHost(); + doReturn(234).when(hostConfiguration3).getPort(); + + doReturn("full").when(hostConfiguration4).getHost(); + doReturn(345).when(hostConfiguration4).getPort(); + + doReturn("common").when(hostConfiguration5).getHost(); + doReturn(456).when(hostConfiguration5).getPort(); + + doReturn("base").when(hostConfiguration6).getHost(); + doReturn(999).when(hostConfiguration6).getPort(); + + doReturn("common").when(hostConfiguration7).getHost(); + doReturn(567).when(hostConfiguration7).getPort(); + + doReturn("common").when(hostConfiguration8).getHost(); + doReturn(678).when(hostConfiguration8).getPort(); + + doReturn("common").when(hostConfiguration9).getHost(); + doReturn(789).when(hostConfiguration9).getPort(); + + doReturn(hostConfigurationCommon).when(httpClientCommon).getHostConfiguration(); + doReturn(hostConfiguration1).when(httpClient1).getHostConfiguration(); + doReturn(hostConfiguration2).when(httpClient2).getHostConfiguration(); + doReturn(hostConfiguration3).when(httpClient3).getHostConfiguration(); + doReturn(hostConfiguration4).when(httpClient4).getHostConfiguration(); + doReturn(hostConfiguration5).when(httpClient5).getHostConfiguration(); + doReturn(hostConfiguration6).when(httpClient6).getHostConfiguration(); + doReturn(hostConfiguration7).when(httpClient7).getHostConfiguration(); + doReturn(hostConfiguration8).when(httpClient8).getHostConfiguration(); + doReturn(hostConfiguration9).when(httpClient9).getHostConfiguration(); + + doReturn(httpClientCommon).when(httpClientFactory).getHttpClient(); + doReturn("common").when(httpClientFactory).getHost(); + doReturn(999).when(httpClientFactory).getPort(); + doReturn(httpClientCommon).when(httpClientFactory).getHttpClient(eq("common"), eq(999)); + doReturn(httpClient1).when(httpClientFactory).getHttpClient(eq("host"), eq(999)); + doReturn(httpClient2).when(httpClientFactory).getHttpClient(eq("common"), eq(123)); + doReturn(httpClient3).when(httpClientFactory).getHttpClient(eq("port"), eq(234)); + doReturn(httpClient4).when(httpClientFactory).getHttpClient(eq("full"), eq(345)); + doReturn(httpClient5).when(httpClientFactory).getHttpClient(eq("common"), eq(456)); + doReturn(httpClient6).when(httpClientFactory).getHttpClient(eq("base"), eq(999)); + doReturn(httpClient7).when(httpClientFactory).getHttpClient(eq("common"), eq(567)); + doReturn(httpClient8).when(httpClientFactory).getHttpClient(eq("common"), eq(678)); + doReturn(httpClient9).when(httpClientFactory).getHttpClient(eq("common"), eq(789)); + + doReturn(httpClientFactory).when(beanFactory).getBean(eq("httpClientFactory")); + + + mapping = new SolrStoreMapping(); + mapping.setBaseUrl("/solr4"); + mapping.setHttpClientFactory("httpClientFactory"); + mapping.setIdentifier(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getIdentifier()); + mapping.setNodeString("host, 123, /woof, port:234, full:345/meep/sheep, 456/cabbage, base/url,,:,:/,:567,:678/,789/more,/"); + mapping.setNumShards(24); + mapping.setProtocol(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getProtocol()); + mapping.setReplicationFactor(3); + wrapper = new SolrStoreMappingWrapper(mapping, beanFactory); + + + + unsharded = new SolrStoreMapping(); + unsharded.setBaseUrl("/solr4"); + unsharded.setHttpClientFactory("httpClientFactory"); + unsharded.setIdentifier(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getIdentifier()); + unsharded.setProtocol(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getProtocol()); + unshardedWrapper = new SolrStoreMappingWrapper(unsharded, beanFactory); + } + + + @Test + public void testBasics() + { + assertEquals(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getIdentifier(), mapping.getIdentifier()); + assertEquals(24, mapping.getNumShards()); + assertEquals(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.getProtocol() , mapping.getProtocol()); + assertEquals(3, mapping.getReplicationFactor()); + assertEquals(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, mapping.getStoreRef()); + assertEquals(12, wrapper.getNodeCount()); + assertTrue(wrapper.isSharded()); + } + + + @Test + public void testShards() + { + String shards = wrapper.getShards(); + assertNotNull(shards); + assertTrue(shards.length() > 0); + String[] fragments = shards.split(","); + assertEquals(mapping.getNumShards(), fragments.length); + } + + @Test + public void testDistribution() + { + // default seed gives /woof + Pair distributor = wrapper.getHttpClientAndBaseUrl(); + assertNotNull(distributor); + assertEquals("/woof-14", distributor.getSecond()); + assertEquals("common", distributor.getFirst().getHostConfiguration().getHost()); + assertEquals(999, distributor.getFirst().getHostConfiguration().getPort()); + } + + @Test + public void testUnsharded() + { + assertTrue(unshardedWrapper.isSharded() == false); + + Pair distributor = unshardedWrapper.getHttpClientAndBaseUrl(); + assertNotNull(distributor); + assertEquals("/solr4", distributor.getSecond()); + assertEquals("common", distributor.getFirst().getHostConfiguration().getHost()); + assertEquals(999, distributor.getFirst().getHostConfiguration().getPort()); + } + +}