org.apache.solr.handler.admin.CoreAdminHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.handler.admin.CoreAdminHandler.java

Source

/*
 * 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.admin;

import java.io.File;
import java.lang.invoke.MethodHandles;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.lang.StringUtils;
import org.apache.solr.api.Api;
import org.apache.solr.cloud.CloudDescriptor;
import org.apache.solr.cloud.ZkController;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.params.CommonAdminParams;
import org.apache.solr.common.params.CoreAdminParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ExecutorUtil;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.CoreDescriptor;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.metrics.SolrMetricManager;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.solr.util.DefaultSolrThreadFactory;
import org.apache.solr.util.stats.MetricUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import static org.apache.solr.common.params.CoreAdminParams.ACTION;
import static org.apache.solr.common.params.CoreAdminParams.CoreAdminAction.STATUS;
import static org.apache.solr.security.PermissionNameProvider.Name.CORE_EDIT_PERM;
import static org.apache.solr.security.PermissionNameProvider.Name.CORE_READ_PERM;

/**
 *
 * @since solr 1.3
 */
public class CoreAdminHandler extends RequestHandlerBase implements PermissionNameProvider {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    protected final CoreContainer coreContainer;
    protected final Map<String, Map<String, TaskObject>> requestStatusMap;
    private final CoreAdminHandlerApi coreAdminHandlerApi;

    protected ExecutorService parallelExecutor = ExecutorUtil.newMDCAwareFixedThreadPool(50,
            new DefaultSolrThreadFactory("parallelCoreAdminExecutor"));

    protected static int MAX_TRACKED_REQUESTS = 100;
    public static String RUNNING = "running";
    public static String COMPLETED = "completed";
    public static String FAILED = "failed";
    public static String RESPONSE = "Response";
    public static String RESPONSE_STATUS = "STATUS";
    public static String RESPONSE_MESSAGE = "msg";

    public CoreAdminHandler() {
        super();
        // Unlike most request handlers, CoreContainer initialization 
        // should happen in the constructor...  
        this.coreContainer = null;
        HashMap<String, Map<String, TaskObject>> map = new HashMap<>(3, 1.0f);
        map.put(RUNNING, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
        map.put(COMPLETED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
        map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
        requestStatusMap = Collections.unmodifiableMap(map);
        coreAdminHandlerApi = new CoreAdminHandlerApi(this);
    }

    /**
     * Overloaded ctor to inject CoreContainer into the handler.
     *
     * @param coreContainer Core Container of the solr webapp installed.
     */
    public CoreAdminHandler(final CoreContainer coreContainer) {
        this.coreContainer = coreContainer;
        HashMap<String, Map<String, TaskObject>> map = new HashMap<>(3, 1.0f);
        map.put(RUNNING, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
        map.put(COMPLETED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
        map.put(FAILED, Collections.synchronizedMap(new LinkedHashMap<String, TaskObject>()));
        requestStatusMap = Collections.unmodifiableMap(map);
        coreAdminHandlerApi = new CoreAdminHandlerApi(this);
    }

    @Override
    final public void init(NamedList args) {
        throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
                "CoreAdminHandler should not be configured in solrconf.xml\n"
                        + "it is a special Handler configured directly by the RequestDispatcher");
    }

    @Override
    public void initializeMetrics(SolrMetricManager manager, String registryName, String scope) {
        super.initializeMetrics(manager, registryName, scope);
        parallelExecutor = MetricUtils.instrumentedExecutorService(parallelExecutor, manager.registry(registryName),
                SolrMetricManager.mkName("parallelCoreAdminExecutor", getCategory().name(), scope, "threadPool"));
    }

    @Override
    public Boolean registerV2() {
        return Boolean.TRUE;
    }

    /**
     * The instance of CoreContainer this handler handles. This should be the CoreContainer instance that created this
     * handler.
     *
     * @return a CoreContainer instance
     */
    public CoreContainer getCoreContainer() {
        return this.coreContainer;
    }

    @Override
    public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
        // Make sure the cores is enabled
        try {
            CoreContainer cores = getCoreContainer();
            if (cores == null) {
                throw new SolrException(ErrorCode.BAD_REQUEST, "Core container instance missing");
            }
            //boolean doPersist = false;
            final String taskId = req.getParams().get(CommonAdminParams.ASYNC);
            final TaskObject taskObject = new TaskObject(taskId);

            if (taskId != null) {
                // Put the tasks into the maps for tracking
                if (getRequestStatusMap(RUNNING).containsKey(taskId)
                        || getRequestStatusMap(COMPLETED).containsKey(taskId)
                        || getRequestStatusMap(FAILED).containsKey(taskId)) {
                    throw new SolrException(ErrorCode.BAD_REQUEST,
                            "Duplicate request with the same requestid found.");
                }

                addTask(RUNNING, taskObject);
            }

            // Pick the action
            CoreAdminOperation op = opMap
                    .get(req.getParams().get(ACTION, STATUS.toString()).toLowerCase(Locale.ROOT));
            if (op == null) {
                handleCustomAction(req, rsp);
                return;
            }

            final CallInfo callInfo = new CallInfo(this, req, rsp, op);
            if (taskId == null) {
                callInfo.call();
            } else {
                try {
                    MDC.put("CoreAdminHandler.asyncId", taskId);
                    MDC.put("CoreAdminHandler.action", op.action.toString());
                    parallelExecutor.execute(() -> {
                        boolean exceptionCaught = false;
                        try {
                            callInfo.call();
                            taskObject.setRspObject(callInfo.rsp);
                        } catch (Exception e) {
                            exceptionCaught = true;
                            taskObject.setRspObjectFromException(e);
                        } finally {
                            removeTask("running", taskObject.taskId);
                            if (exceptionCaught) {
                                addTask("failed", taskObject, true);
                            } else
                                addTask("completed", taskObject, true);
                        }
                    });
                } finally {
                    MDC.remove("CoreAdminHandler.asyncId");
                    MDC.remove("CoreAdminHandler.action");
                }
            }
        } finally {
            rsp.setHttpCaching(false);

        }
    }

    /**
     * Handle Custom Action.
     * <p>
     * This method could be overridden by derived classes to handle custom actions. <br> By default - this method throws a
     * solr exception. Derived classes are free to write their derivation if necessary.
     */
    protected void handleCustomAction(SolrQueryRequest req, SolrQueryResponse rsp) {
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
                "Unsupported operation: " + req.getParams().get(ACTION));
    }

    public static ImmutableMap<String, String> paramToProp = ImmutableMap.<String, String>builder()
            .put(CoreAdminParams.CONFIG, CoreDescriptor.CORE_CONFIG)
            .put(CoreAdminParams.SCHEMA, CoreDescriptor.CORE_SCHEMA)
            .put(CoreAdminParams.DATA_DIR, CoreDescriptor.CORE_DATADIR)
            .put(CoreAdminParams.ULOG_DIR, CoreDescriptor.CORE_ULOGDIR)
            .put(CoreAdminParams.CONFIGSET, CoreDescriptor.CORE_CONFIGSET)
            .put(CoreAdminParams.LOAD_ON_STARTUP, CoreDescriptor.CORE_LOADONSTARTUP)
            .put(CoreAdminParams.TRANSIENT, CoreDescriptor.CORE_TRANSIENT)
            .put(CoreAdminParams.SHARD, CoreDescriptor.CORE_SHARD)
            .put(CoreAdminParams.COLLECTION, CoreDescriptor.CORE_COLLECTION)
            .put(CoreAdminParams.ROLES, CoreDescriptor.CORE_ROLES)
            .put(CoreAdminParams.CORE_NODE_NAME, CoreDescriptor.CORE_NODE_NAME)
            .put(ZkStateReader.NUM_SHARDS_PROP, CloudDescriptor.NUM_SHARDS).build();

    protected static Map<String, String> buildCoreParams(SolrParams params) {

        Map<String, String> coreParams = new HashMap<>();

        // standard core create parameters
        for (String param : paramToProp.keySet()) {
            String value = params.get(param, null);
            if (StringUtils.isNotEmpty(value)) {
                coreParams.put(paramToProp.get(param), value);
            }
        }

        // extra properties
        Iterator<String> paramsIt = params.getParameterNamesIterator();
        while (paramsIt.hasNext()) {
            String param = paramsIt.next();
            if (param.startsWith(CoreAdminParams.PROPERTY_PREFIX)) {
                String propName = param.substring(CoreAdminParams.PROPERTY_PREFIX.length());
                String propValue = params.get(param);
                coreParams.put(propName, propValue);
            }
            if (param.startsWith(ZkController.COLLECTION_PARAM_PREFIX)) {
                coreParams.put(param, params.get(param));
            }
        }

        return coreParams;
    }

    protected static String normalizePath(String path) {
        if (path == null)
            return null;
        path = path.replace('/', File.separatorChar);
        path = path.replace('\\', File.separatorChar);
        return path;
    }

    public static ModifiableSolrParams params(String... params) {
        ModifiableSolrParams msp = new ModifiableSolrParams();
        for (int i = 0; i < params.length; i += 2) {
            msp.add(params[i], params[i + 1]);
        }
        return msp;
    }

    //////////////////////// SolrInfoMBeans methods //////////////////////

    @Override
    public String getDescription() {
        return "Manage Multiple Solr Cores";
    }

    @Override
    public Category getCategory() {
        return Category.ADMIN;
    }

    @Override
    public Name getPermissionName(AuthorizationContext ctx) {
        String action = ctx.getParams().get(CoreAdminParams.ACTION);
        if (action == null)
            return CORE_READ_PERM;
        CoreAdminParams.CoreAdminAction coreAction = CoreAdminParams.CoreAdminAction.get(action);
        if (coreAction == null)
            return CORE_READ_PERM;
        return coreAction.isRead ? CORE_READ_PERM : CORE_EDIT_PERM;
    }

    /**
     * Helper class to manage the tasks to be tracked.
     * This contains the taskId, request and the response (if available).
     */
    static class TaskObject {
        String taskId;
        String rspInfo;

        public TaskObject(String taskId) {
            this.taskId = taskId;
        }

        public String getRspObject() {
            return rspInfo;
        }

        public void setRspObject(SolrQueryResponse rspObject) {
            this.rspInfo = rspObject.getToLogAsString("TaskId: " + this.taskId);
        }

        public void setRspObjectFromException(Exception e) {
            this.rspInfo = e.getMessage();
        }
    }

    /**
     * Helper method to add a task to a tracking type.
     */
    void addTask(String type, TaskObject o, boolean limit) {
        synchronized (getRequestStatusMap(type)) {
            if (limit && getRequestStatusMap(type).size() == MAX_TRACKED_REQUESTS) {
                String key = getRequestStatusMap(type).entrySet().iterator().next().getKey();
                getRequestStatusMap(type).remove(key);
            }
            addTask(type, o);
        }
    }

    private void addTask(String type, TaskObject o) {
        synchronized (getRequestStatusMap(type)) {
            getRequestStatusMap(type).put(o.taskId, o);
        }
    }

    /**
     * Helper method to remove a task from a tracking map.
     */
    private void removeTask(String map, String taskId) {
        synchronized (getRequestStatusMap(map)) {
            getRequestStatusMap(map).remove(taskId);
        }
    }

    /**
     * Helper method to get a request status map given the name.
     */
    Map<String, TaskObject> getRequestStatusMap(String key) {
        return requestStatusMap.get(key);
    }

    /**
     * Method to ensure shutting down of the ThreadPool Executor.
     */
    public void shutdown() {
        if (parallelExecutor != null && !parallelExecutor.isShutdown())
            ExecutorUtil.shutdownAndAwaitTermination(parallelExecutor);
    }

    private static final Map<String, CoreAdminOperation> opMap = new HashMap<>();

    static class CallInfo {
        final CoreAdminHandler handler;
        final SolrQueryRequest req;
        final SolrQueryResponse rsp;
        final CoreAdminOperation op;

        CallInfo(CoreAdminHandler handler, SolrQueryRequest req, SolrQueryResponse rsp, CoreAdminOperation op) {
            this.handler = handler;
            this.req = req;
            this.rsp = rsp;
            this.op = op;
        }

        void call() throws Exception {
            op.execute(this);
        }

    }

    @Override
    public Collection<Api> getApis() {
        return coreAdminHandlerApi.getApis();
    }

    static {
        for (CoreAdminOperation op : CoreAdminOperation.values())
            opMap.put(op.action.toString().toLowerCase(Locale.ROOT), op);
    }

    /**
     * used by the INVOKE action of core admin handler
     */
    public interface Invocable {
        Map<String, Object> invoke(SolrQueryRequest req);
    }

    interface CoreAdminOp {
        void execute(CallInfo it) throws Exception;
    }
}