org.sakaiproject.nakamura.solr.MultiMasterSolrClient.java Source code

Java tutorial

Introduction

Here is the source code for org.sakaiproject.nakamura.solr.MultiMasterSolrClient.java

Source

/**
 * Licensed to the Sakai Foundation (SF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The SF 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.sakaiproject.nakamura.solr;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.apache.commons.io.IOUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.solr.client.solrj.SolrServer;
import org.apache.solr.client.solrj.embedded.EmbeddedSolrServer;
import org.apache.solr.client.solrj.impl.BinaryResponseParser;
import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.core.NakamuraSolrConfig;
import org.apache.solr.core.SolrConfig;
import org.apache.solr.core.SolrCore;
import org.apache.solr.schema.IndexSchema;
import org.osgi.framework.BundleContext;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.ComponentContext;
import org.sakaiproject.nakamura.api.solr.SolrClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMap.Builder;

@Component(immediate = true, metatype = true)
@Service(value = SolrClient.class)
public class MultiMasterSolrClient implements SolrClient {

    private static final Logger LOGGER = LoggerFactory.getLogger(MultiMasterSolrClient.class);
    @Property(value = SolrClient.MULTI)
    public static final String CLIENT_NAME = SolrClient.CLIENT_NAME;

    @Property(value = "embedded")
    private static final String PROP_CLUSTER_CONFIG_MODE = "cluster.config.mode";

    @Property(value = "solrconfig.xml")
    private static final String PROP_SOLR_CONFIG = "solrconfig";
    @Property(value = "solrconfig.xml")
    private static final String PROP_SOLR_SCHEMA = "solrschema";

    @Property(value = "http://localhost:8983/solr")
    private static final String PROP_SOLR_URL = "remoteurl";

    @Property(intValue = 1)
    private static final String PROP_MAX_RETRIES = "max.retries";

    @Property(boolValue = true)
    private static final String PROP_ALLOW_COMPRESSION = "allow.compression";

    @Property(boolValue = false)
    private static final String PROP_FOLLOW = "follow.redirects";

    @Property(intValue = 100)
    private static final String PROP_MAX_TOTAL_CONNECTONS = "max.total.connections";

    @Property(intValue = 100)
    private static final String PROP_MAX_CONNECTONS_PER_HOST = "max.connections.per.host";

    @Property(intValue = 100)
    private static final String PROP_CONNECTION_TIMEOUT = "connection.timeout";

    @Property(intValue = 1000)
    private static final String PROP_SO_TIMEOUT = "socket.timeout";

    private static final String LOGGER_KEY = "org.sakaiproject.nakamura.logger";
    private static final String LOGGER_VAL = "org.apache.solr";
    public static final String NAKAMURA = "nakamura";

    /**
      * According to the doc, this is thread safe and must be shared between all
      * threads.
      */
    private EmbeddedSolrServer server;
    /**
     * This should be the Solr server that accepts updates. This might be local
     * or it might be remote, depending on election.
     */
    private ThreadLocal<SolrServer> updateServer = new ThreadLocal<SolrServer>();
    private String solrHome;
    private CoreContainer coreContainer;
    private SolrCore nakamuraCore;

    @Reference
    protected ConfigurationAdmin configurationAdmin;
    private Map<String, Object> multiMasterProperties;
    private boolean enabled;
    private SolrClientListener listener;

    @Activate
    public void activate(ComponentContext componentContext)
            throws IOException, ParserConfigurationException, SAXException {
        BundleContext bundleContext = componentContext.getBundleContext();
        solrHome = Utils.getSolrHome(bundleContext);
        multiMasterProperties = getMultiMasterProperties(toMap(componentContext.getProperties()));
    }

    public void enable(SolrClientListener listener) throws IOException, ParserConfigurationException, SAXException {
        if (enabled) {
            return;
        }

        String configLocation = "solrconfig.xml";
        String schemaLocation = "schema.xml";
        if (multiMasterProperties != null) {
            configLocation = (String) multiMasterProperties.get(PROP_SOLR_CONFIG);
            schemaLocation = (String) multiMasterProperties.get(PROP_SOLR_SCHEMA);
        }

        // Note that the following property could be set through JVM level
        // arguments too
        LOGGER.debug("Logger for Embedded Solr is in {slinghome}/log/solr.log at level INFO");
        Configuration logConfiguration = getLogConfiguration();

        // create a log configuration if none was found. leave alone any found
        // configurations
        // so that modifications will persist between server restarts
        if (logConfiguration == null) {
            logConfiguration = configurationAdmin
                    .createFactoryConfiguration("org.apache.sling.commons.log.LogManager.factory.config", null);
            Dictionary<String, Object> loggingProperties = new Hashtable<String, Object>();
            loggingProperties.put("org.apache.sling.commons.log.level", "INFO");
            loggingProperties.put("org.apache.sling.commons.log.file", "logs/solr.log");
            loggingProperties.put("org.apache.sling.commons.log.names", "org.apache.solr");
            // add this property to give us something unique to re-find this
            // configuration
            loggingProperties.put(LOGGER_KEY, LOGGER_VAL);
            logConfiguration.update(loggingProperties);
        }

        System.setProperty("solr.solr.home", solrHome);
        File solrHomeFile = new File(solrHome);
        File coreDir = new File(solrHomeFile, NAKAMURA);
        // File coreConfigDir = new File(solrHomeFile,"conf");
        deployFile(solrHomeFile, "solr.xml");
        // deployFile(coreConfigDir,"solrconfig.xml");
        // deployFile(coreConfigDir,"schema.xml");
        ClassLoader contextClassloader = Thread.currentThread().getContextClassLoader();
        Thread.currentThread().setContextClassLoader(this.getClass().getClassLoader());
        ClosableInputSource schemaSource = null;
        ClosableInputSource configSource = null;
        try {
            NakamuraSolrResourceLoader loader = new NakamuraSolrResourceLoader(solrHome,
                    this.getClass().getClassLoader());
            coreContainer = new CoreContainer(loader);
            configSource = getStream(configLocation);
            schemaSource = getStream(schemaLocation);
            SolrConfig config = new NakamuraSolrConfig(loader, configLocation, configSource);
            IndexSchema schema = new IndexSchema(config, null, schemaSource);
            nakamuraCore = new SolrCore(NAKAMURA, coreDir.getAbsolutePath(), config, schema, null);
            coreContainer.register(NAKAMURA, nakamuraCore, false);
            server = new EmbeddedSolrServer(coreContainer, NAKAMURA);
            LoggerFactory.getLogger(this.getClass()).info("Contans cores {} ", coreContainer.getCoreNames());
        } finally {
            Thread.currentThread().setContextClassLoader(contextClassloader);
            safeClose(schemaSource);
            safeClose(configSource);
        }

        this.enabled = true;
        this.listener = listener;
    }

    private void safeClose(ClosableInputSource source) {
        if (source != null) {
            try {
                source.close();
            } catch (IOException e) {
                LOGGER.debug(e.getMessage(), e);
            }
        }
    }

    /**
     * @param map
     * @return
     */
    private Map<String, Object> getMultiMasterProperties(Map<String, Object> properties) {
        String clusterConfigMode = Utils.toString(properties.get(PROP_CLUSTER_CONFIG_MODE), "election");
        if ("embedded".equals(clusterConfigMode)) {
            return null;
        } else if ("election".equals(clusterConfigMode)) {
            // perform an election and get the properties from the master node.
            // TODO
        }
        // is the config mode is not election or embedded, then its configured
        // on a per node
        // basis and already in the properties.
        // properties contain the master configuration.
        return properties;
    }

    private Map<String, Object> toMap(@SuppressWarnings("rawtypes") Dictionary properties) {
        Builder<String, Object> b = ImmutableMap.builder();
        for (Enumeration<?> e = properties.keys(); e.hasMoreElements();) {
            String k = (String) e.nextElement();
            b.put(k, properties.get(k));
        }
        return b.build();
    }

    private SolrServer createUpdateServer(Map<String, Object> properties) throws MalformedURLException {
        String url = Utils.toString(properties.get(PROP_SOLR_URL), "http://localhost:8983/solr");

        CommonsHttpSolrServer remoteServer = new CommonsHttpSolrServer(url);
        remoteServer.setSoTimeout(Utils.toInt(properties.get(PROP_SO_TIMEOUT), 1000)); // socket
        // read
        // timeout
        remoteServer.setConnectionTimeout(Utils.toInt(properties.get(PROP_CONNECTION_TIMEOUT), 100));
        remoteServer
                .setDefaultMaxConnectionsPerHost(Utils.toInt(properties.get(PROP_MAX_CONNECTONS_PER_HOST), 100));
        remoteServer.setMaxTotalConnections(Utils.toInt(properties.get(PROP_MAX_TOTAL_CONNECTONS), 100));
        remoteServer.setFollowRedirects(Utils.toBoolean(properties.get(PROP_FOLLOW), false)); // defaults
        // to
        // false
        // allowCompression defaults to false.
        // Server side must support gzip or deflate for this to have any effect.
        remoteServer.setAllowCompression(Utils.toBoolean(properties.get(PROP_ALLOW_COMPRESSION), true));
        remoteServer.setMaxRetries(Utils.toInt(properties.get(PROP_MAX_RETRIES), 1)); // defaults
        // to 0.
        // > 1
        // not
        // recommended.
        remoteServer.setParser(new BinaryResponseParser()); // binary parser is
        // used by
        // default

        return remoteServer;
    }

    private Configuration getLogConfiguration() throws IOException {
        Configuration logConfiguration = null;
        try {
            Configuration[] configs = configurationAdmin
                    .listConfigurations("(" + LOGGER_KEY + "=" + LOGGER_VAL + ")");
            if (configs != null && configs.length > 0) {
                logConfiguration = configs[0];
            }
        } catch (InvalidSyntaxException e) {
            // ignore this as we'll create what we need
        }
        return logConfiguration;
    }

    private ClosableInputSource getStream(String name) throws IOException {
        if (name.contains(":")) {
            // try a URL
            try {
                URL u = new URL(name);
                InputStream in = u.openStream();
                if (in != null) {
                    return new ClosableInputSource(in);
                }
            } catch (IOException e) {
                LOGGER.debug(e.getMessage(), e);
            }
        }
        // try a file
        File f = new File(name);
        if (f.exists()) {
            return new ClosableInputSource(new FileInputStream(f));
        } else {
            // try classpath
            InputStream in = this.getClass().getClassLoader().getResourceAsStream(name);
            if (in == null) {
                LOGGER.error("Failed to locate stream {}, tried URL, filesystem ", name);
                throw new IOException("Failed to locate stream " + name + ", tried URL, filesystem ");
            }
            return new ClosableInputSource(in);
        }
    }

    private void deployFile(File destDir, String target) throws IOException {
        if (!destDir.isDirectory() && !destDir.mkdirs()) {
            LOGGER.warn("Unable to create dest dir {} for {}, may cause later problems ", destDir, target);
        }
        File destFile = new File(destDir, target);
        if (!destFile.exists()) {
            InputStream in = null;
            OutputStream out = null;

            try {
                in = Utils.class.getClassLoader().getResourceAsStream(target);
                out = new FileOutputStream(destFile);
                IOUtils.copy(in, out);
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (Exception e) {
                    }
                }

                if (in != null) {
                    try {
                        in.close();
                    } catch (Exception e) {
                    }
                }
            }
        }
    }

    @Deactivate
    public void deactivate(ComponentContext componentContext) {
        disable();
    }

    public void disable() {
        if (!enabled) {
            return;
        }
        nakamuraCore.close();
        coreContainer.shutdown();
        enabled = false;
        if (listener != null) {
            listener.disabled();
        }
    }

    public SolrServer getServer() {
        return server;
    }

    public SolrServer getUpdateServer() {
        SolrServer solrServer = updateServer.get();
        if (solrServer == null) {
            if (multiMasterProperties == null) {
                solrServer = server;
            } else {
                try {
                    solrServer = createUpdateServer(multiMasterProperties);
                } catch (MalformedURLException e) {
                    LOGGER.error(e.getMessage(), e);
                    return null;
                }
            }
            updateServer.set(solrServer);
        }
        return solrServer;
    }

    public String getSolrHome() {
        return solrHome;
    }

    public String getName() {
        return MULTI;
    }

}