blob: df45b4a636c4ee804239194a841b6bc2d21897b5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.solr.handler;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.solr.api.Command;
import org.apache.solr.api.EndPoint;
import org.apache.solr.api.PayloadObj;
import org.apache.solr.client.solrj.request.beans.BackupCollectionPayload;
import org.apache.solr.client.solrj.request.beans.CreateAliasPayload;
import org.apache.solr.client.solrj.request.beans.CreatePayload;
import org.apache.solr.client.solrj.request.beans.DeleteAliasPayload;
import org.apache.solr.client.solrj.request.beans.RestoreCollectionPayload;
import org.apache.solr.client.solrj.request.beans.SetAliasPropertyPayload;
import org.apache.solr.client.solrj.request.beans.V2ApiConstants;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CollectionAdminParams;
import org.apache.solr.common.params.CollectionParams.CollectionAction;
import org.apache.solr.handler.admin.CollectionsHandler;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import static org.apache.solr.client.solrj.SolrRequest.METHOD.*;
import static org.apache.solr.client.solrj.request.beans.V2ApiConstants.ROUTER_KEY;
import static org.apache.solr.cloud.api.collections.RoutedAlias.CREATE_COLLECTION_PREFIX;
import static org.apache.solr.common.params.CollectionAdminParams.PROPERTY_PREFIX;
import static org.apache.solr.common.params.CollectionAdminParams.ROUTER_PREFIX;
import static org.apache.solr.common.params.CommonParams.ACTION;
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.handler.ClusterAPI.wrapParams;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.COLL_READ_PERM;
/**
* All V2 APIs for collection management
*/
public class CollectionsAPI {
public static final String V2_CREATE_COLLECTION_CMD = "create";
public static final String V2_BACKUP_CMD = "backup-collection";
public static final String V2_RESTORE_CMD = "restore-collection";
public static final String V2_CREATE_ALIAS_CMD = "create-alias";
public static final String V2_SET_ALIAS_PROP_CMD = "set-alias-property";
public static final String V2_DELETE_ALIAS_CMD = "delete-alias";
private final CollectionsHandler collectionsHandler;
public final CollectionsCommands collectionsCommands = new CollectionsCommands();
public CollectionsAPI(CollectionsHandler collectionsHandler) {
this.collectionsHandler = collectionsHandler;
}
@EndPoint(
path = {"/c", "/collections"},
method = GET,
permission = COLL_READ_PERM)
public void getCollections(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
CollectionsHandler.CollectionOperation.LIST_OP.execute(req, rsp, collectionsHandler);
}
@EndPoint(
path = {"/c", "/collections"},
method = POST,
permission = COLL_EDIT_PERM)
public class CollectionsCommands {
@Command(name = V2_BACKUP_CMD)
@SuppressWarnings("unchecked")
public void backupCollection(PayloadObj<BackupCollectionPayload> obj) throws Exception {
final Map<String, Object> v1Params = obj.get().toMap(new HashMap<>());
v1Params.put(ACTION, CollectionAction.BACKUP.toLower());
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
@Command(name = V2_RESTORE_CMD)
@SuppressWarnings("unchecked")
public void restoreBackup(PayloadObj<RestoreCollectionPayload> obj) throws Exception {
final RestoreCollectionPayload v2Body = obj.get();
final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
v1Params.put(ACTION, CollectionAction.RESTORE.toLower());
if (v2Body.createCollectionParams != null && !v2Body.createCollectionParams.isEmpty()) {
final Map<String, Object> createCollParams = (Map<String, Object>) v1Params.remove(V2ApiConstants.CREATE_COLLECTION_KEY);
convertV2CreateCollectionMapToV1ParamMap(createCollParams);
v1Params.putAll(createCollParams);
}
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
@Command(name = V2_CREATE_ALIAS_CMD)
@SuppressWarnings("unchecked")
public void createAlias(PayloadObj<CreateAliasPayload> obj) throws Exception {
final CreateAliasPayload v2Body = obj.get();
final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
v1Params.put(ACTION, CollectionAction.CREATEALIAS.toLower());
if (! CollectionUtils.isEmpty(v2Body.collections)) {
final String collectionsStr = String.join(",", v2Body.collections);
v1Params.remove(V2ApiConstants.COLLECTIONS);
v1Params.put(V2ApiConstants.COLLECTIONS, collectionsStr);
}
if (v2Body.router != null) {
Map<String, Object> routerProperties = (Map<String, Object>) v1Params.remove(V2ApiConstants.ROUTER_KEY);
flattenMapWithPrefix(routerProperties, v1Params, ROUTER_PREFIX);
}
if (v2Body.createCollectionParams != null && !v2Body.createCollectionParams.isEmpty()) {
final Map<String, Object> createCollectionMap = (Map<String, Object>) v1Params.remove(V2ApiConstants.CREATE_COLLECTION_KEY);
convertV2CreateCollectionMapToV1ParamMap(createCollectionMap);
flattenMapWithPrefix(createCollectionMap, v1Params, CREATE_COLLECTION_PREFIX);
}
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
@Command(name= V2_SET_ALIAS_PROP_CMD)
@SuppressWarnings("unchecked")
public void setAliasProperty(PayloadObj<SetAliasPropertyPayload> obj) throws Exception {
final SetAliasPropertyPayload v2Body = obj.get();
final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
v1Params.put(ACTION, CollectionAction.ALIASPROP.toLower());
// Flatten "properties" map into individual prefixed params
final Map<String, Object> propertiesMap = (Map<String, Object>) v1Params.remove(V2ApiConstants.PROPERTIES_KEY);
flattenMapWithPrefix(propertiesMap, v1Params, PROPERTY_PREFIX);
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
@Command(name= V2_DELETE_ALIAS_CMD)
@SuppressWarnings("unchecked")
public void deleteAlias(PayloadObj<DeleteAliasPayload> obj) throws Exception {
final DeleteAliasPayload v2Body = obj.get();
final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
v1Params.put(ACTION, CollectionAction.DELETEALIAS.toLower());
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
@Command(name = V2_CREATE_COLLECTION_CMD)
@SuppressWarnings("unchecked")
public void create(PayloadObj<CreatePayload> obj) throws Exception {
final CreatePayload v2Body = obj.get();
final Map<String, Object> v1Params = v2Body.toMap(new HashMap<>());
v1Params.put(ACTION, CollectionAction.CREATE.toLower());
convertV2CreateCollectionMapToV1ParamMap(v1Params);
collectionsHandler.handleRequestBody(wrapParams(obj.getRequest(), v1Params), obj.getResponse());
}
@SuppressWarnings("unchecked")
private void convertV2CreateCollectionMapToV1ParamMap(Map<String, Object> v2MapVals) {
// Keys are copied so that map can be modified as keys are looped through.
final Set<String> v2Keys = v2MapVals.keySet().stream().collect(Collectors.toSet());
for (String key : v2Keys) {
switch (key) {
case V2ApiConstants.PROPERTIES_KEY:
final Map<String, Object> propertiesMap = (Map<String, Object>) v2MapVals.remove(V2ApiConstants.PROPERTIES_KEY);
flattenMapWithPrefix(propertiesMap, v2MapVals, PROPERTY_PREFIX);
break;
case ROUTER_KEY:
final Map<String, Object> routerProperties = (Map<String, Object>) v2MapVals.remove(V2ApiConstants.ROUTER_KEY);
flattenMapWithPrefix(routerProperties, v2MapVals, CollectionAdminParams.ROUTER_PREFIX);
break;
case V2ApiConstants.CONFIG:
v2MapVals.put(CollectionAdminParams.COLL_CONF, v2MapVals.remove(V2ApiConstants.CONFIG));
break;
case V2ApiConstants.SHUFFLE_NODES:
v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_SHUFFLE_PARAM, v2MapVals.remove(V2ApiConstants.SHUFFLE_NODES));
break;
case V2ApiConstants.NODE_SET:
final Object nodeSetValUncast = v2MapVals.remove(V2ApiConstants.NODE_SET);
if (nodeSetValUncast instanceof String) {
v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_PARAM, nodeSetValUncast);
} else {
final List<String> nodeSetList = (List<String>) nodeSetValUncast;
final String nodeSetStr = String.join(",", nodeSetList);
v2MapVals.put(CollectionAdminParams.CREATE_NODE_SET_PARAM, nodeSetStr);
}
break;
default:
break;
}
}
}
private void flattenMapWithPrefix(Map<String, Object> toFlatten, Map<String, Object> destination,
String additionalPrefix) {
if (toFlatten == null || toFlatten.isEmpty() || destination == null) {
return;
}
toFlatten.forEach((k, v) -> destination.put(additionalPrefix + k, v));
}
}
@EndPoint(path = {"/c/{collection}", "/collections/{collection}"},
method = DELETE,
permission = COLL_EDIT_PERM)
public void deleteCollection(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
req = wrapParams(req, ACTION,
CollectionAction.DELETE.toString(),
NAME, req.getPathTemplateValues().get(ZkStateReader.COLLECTION_PROP));
collectionsHandler.handleRequestBody(req, rsp);
}
}