Build Web Service APIs to Update Solr's Managed Resources (stop words, synonyms)


User Case
Solr provides Rest API to update managed resources such as stop words, synonyms and etc. But usually we will wrap it and provide Rest api in our application layer, so admin can do solr admin operation in UI.

Also usually we use CloudSolrClient and prefer to use solrj api over make directly rest api to solr server - as usually our application only knows address of zookeeper  servers, not address of solr servers.

The Implementation
We first create generic APIs: getManagedResource, addManagedResource and deleteManagedResource. Then we call them to manage stop words, synonyms.

We use spring-data-solr's SolrJsonRequest in getManagedResource and addManagedResource which can help parse json's response.

deleteManagedResource is more complex - we can't use solrJ directly
as org.apache.solr.client.solrj.SolrRequest.METHOD only supports GET, POST, PUT not DELETE.

Here I use CloudSolrClient's Apache HttpClient to send HttpDelete, use ZkStateReader and ClusterState to get one address of live solr nodes.

The Rest APIs just call these methods.

public String getManagedResource(final String path) {
    try {
        final SolrJsonRequest request = new SolrJsonRequest(METHOD.GET, path);
        return request.process(this.getSolrClient()).getJsonResponse();
    } catch (SolrServerException | IOException e) {
        throw new MyServerException(e, "Failed to get " + path);
    }
}
public void addManagedResource(final String path, final Object content, final boolean reloadCollection) {
    final SolrJsonRequest request = new SolrJsonRequest(METHOD.PUT, path);
    request.addContentToStream(content);
    try {
        final SolrJsonResponse response = request.process(this.getSolrClient());
        final int status = response.getStatus();
        logger.info(MessageFormat.format("add resource: {0}, status: {1}, result: {2}", path, status,
                response.getJsonResponse()));
        if (status != 0) {
            throw new MyServerException(ErrorCode.data_access_error,
                    MessageFormat.format("Failed to add resource, path: {0}, status: {1}", path, status));
        }
        if (reloadCollection) {
            reloadCollection();
        }
    } catch (SolrServerException | IOException | InterruptedException e) {
        throw new MyServerException(e, "Failed to add resource: " + path);
    }
}
public void deleteManagedResource(@Nonnull final List<String> paths, final boolean reloadCollection) {
    try {
        Preconditions.checkNotNull(paths);
        final String solrUrl = getOneSolrServerUrl(getSolrClient());
        final List<String> done = new ArrayList<>(paths.size());
        for (final String path : paths) {
            final HttpDelete request = new HttpDelete(solrUrl + path);

            final HttpResponse response = getSolrClient().getHttpClient().execute(request);
            final String entity = EntityUtils.toString(response.getEntity());
            logger.info(MessageFormat.format("delete path: {0}, result: {1}", path, entity));
            final ObjectMapper objectMapper = Util.createFailSafeObjectmapper();
            final Map<String, Object> resultMap = objectMapper.readValue(entity,
                    objectMapper.getTypeFactory().constructMapLikeType(Map.class, String.class, Object.class));
            final Map<String, Object> responseHeader = (Map<String, Object>) resultMap.get("responseHeader");
            if (responseHeader != null) {
                final int status = Integer.valueOf(responseHeader.get("status").toString());
                // ignore 404 which means it's already deleted
                if (status != 0 && status != 404) {
                    throw new MyServerException(MessageFormat.format(
                            "Failed to delete path: {0}, status: {1}, already deleted: {2}", path, status, done));
                }
            }
            done.add(path);
        }
        if (reloadCollection) {
            this.reloadCollection();
        }
    } catch (IOException | SolrServerException | InterruptedException e) {
        throw new MyServerException(ErrorCode.data_access_error, e, "Failed to delete path: " + paths);
    }
}

public String getSynonyms(final String language) {
    return getManagedResource("/schema/analysis/synonyms/" + language);
}
public void addSynonyms(final String language, final List<Object> synonymes, final boolean reloadCollection) {
    for(Object synonyms: synonymes){
        addManagedResource("/schema/analysis/synonyms/" + language, synonyms, reloadCollection);
    }
}
public void deleteSynonyms(final String language, final List<String> synonyms, final boolean reloadCollection) {
    if (CollectionUtils.isNotEmpty(synonyms)) {
        deleteManagedResource(synonyms.stream().map(synonym -> {
            return MessageFormat.format("/schema/analysis/synonyms/{0}/{1}", language,
                    Util.encodeAsUtf8(synonym));
        }).collect(Collectors.toList()), reloadCollection);
    }
}

public void addStopWords(final String language, final List<String> stopWords, final boolean reloadCollection) {
    addManagedResource("/schema/analysis/stopwords/" + language, stopWords, reloadCollection);
}
public String getStopWords(final String language) {
    return getManagedResource("/schema/analysis/stopwords/" + language);
}
public void deleteStopWords(final String language, final List<String> stopWords, final boolean reloadCollection) {
    if (CollectionUtils.isNotEmpty(stopWords)) {
        deleteManagedResource(stopWords.stream().map(synonym -> {
            return MessageFormat.format("/schema/analysis/stopwords/{0}/{1}", language,
                    Util.encodeAsUtf8(synonym));
        }).collect(Collectors.toList()), reloadCollection);
    }
}


public static String getOneSolrServerUrl(CloudSolrClient solrClient)
{
    final ZkStateReader zkReader =solrClient.getZkStateReader();
    final ClusterState clusterState = zkReader.getClusterState();
    final SetString> liveNodes = clusterState.getLiveNodes();
    
    if (liveNodes.isEmpty()) {
        throw new MyServerException(ErrorCode.data_access_error, "No lobe nodes");
    }
    return zkReader.getBaseUrlForNodeName(new TreeSet<>(liveNodes).iterator().next()) + "/" + solrClient.getDefaultCollection();   
}

Labels

adsense (5) Algorithm (69) Algorithm Series (35) Android (7) ANT (6) bat (8) Big Data (7) Blogger (14) Bugs (6) Cache (5) Chrome (19) Code Example (29) Code Quality (7) Coding Skills (5) Database (7) Debug (16) Design (5) Dev Tips (63) Eclipse (32) Git (5) Google (33) Guava (7) How to (9) Http Client (8) IDE (7) Interview (88) J2EE (13) J2SE (49) Java (186) JavaScript (27) JSON (7) Learning code (9) Lesson Learned (6) Linux (26) Lucene-Solr (112) Mac (10) Maven (8) Network (9) Nutch2 (18) Performance (9) PowerShell (11) Problem Solving (11) Programmer Skills (6) regex (5) Scala (6) Security (9) Soft Skills (38) Spring (22) System Design (11) Testing (7) Text Mining (14) Tips (17) Tools (24) Troubleshooting (29) UIMA (9) Web Development (19) Windows (21) xml (5)