All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.cinchapi.concourse.server.plugin.Plugin Maven / Gradle / Ivy

/*
 * Copyright (c) 2013-2018 Cinchapi Inc.
 *
 * Licensed 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 com.cinchapi.concourse.server.plugin;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.nio.ByteBuffer;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ConcurrentMap;

import javax.annotation.concurrent.Immutable;

import org.apache.commons.io.output.TeeOutputStream;

import com.cinchapi.common.base.CheckedExceptions;
import com.cinchapi.common.io.Files;
import com.cinchapi.common.logging.Logger;
import com.cinchapi.concourse.server.plugin.io.InterProcessCommunication;
import com.cinchapi.concourse.server.plugin.io.MessageQueue;
import com.cinchapi.concourse.server.plugin.io.PluginSerializer;
import com.cinchapi.concourse.thrift.AccessToken;
import com.cinchapi.concourse.util.ConcurrentMaps;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding;

/**
 * A {@link Plugin} extends the functionality of Concourse Server.
 * 

* Each class that extends this one may define methods that can be dynamically * invoked using the * {@link com.cinchapi.concourse.Concourse#invokePlugin(String, String, Object...) * invokePlugin} method. *

* * @author Jeff Nelson */ public abstract class Plugin { /** * The {@link AccessToken} that the plugin should use when making non-user * (i.e. service) requests to Concourse Server. */ public final static AccessToken SERVICE_TOKEN; /** * The name of the dynamic property that is passed to the plugin's JVM to * instruct it as to where the plugin's home is located. */ protected final static String PLUGIN_HOME_JVM_PROPERTY = "com.cinchapi.concourse.plugin.home"; /** * The name of the dynamic property that is passed to the plugin's JVM to * instruct it as to what {@link AccessToken} to use for service-based * server requests. */ protected final static String PLUGIN_SERVICE_TOKEN_JVM_PROPERTY = "com.cinchapi.concourse.plugin.token"; static { // Read the service token from the system properties String encoded = System.getProperty(PLUGIN_SERVICE_TOKEN_JVM_PROPERTY); if(encoded != null) { byte[] decoded = BaseEncoding.base32Hex().decode(encoded); ByteBuffer bytes = ByteBuffer.wrap(decoded); SERVICE_TOKEN = new AccessToken(bytes); } else { SERVICE_TOKEN = null; } } /** * The communication channel for messages that come from Concourse Server, */ protected final InterProcessCommunication fromServer; /** * A {@link Logger} for plugin operations. */ protected final Logger log; /** * A reference to the local Concourse Server {@link ConcourseRuntime * runtime} to which this plugin is registered. */ protected final ConcourseRuntime runtime; /** * Responsible for taking arbitrary objects and turning them into binary so * they can be sent across the wire. */ protected final PluginSerializer serializer = new PluginSerializer(); /** * The communication channel for messages that are sent by this * {@link Plugin} to Concourse Server. */ private final InterProcessCommunication fromPlugin; /** * Upstream response from Concourse Server in response to requests made via * {@link ConcourseRuntime}. */ private final ConcurrentMap fromServerResponses; /** * A boolean that tracks whether the ready state has been set. */ private boolean inReadyState = false; /** * Construct a new instance. * * @param fromServer the location where Concourse Server places messages to * be consumed by the Plugin * @param fromPlugin the location where the Plugin places messages to be * consumed by Concourse Server */ public Plugin(String fromServer, String fromPlugin) { this.runtime = ConcourseRuntime.getRuntime(); this.fromServer = new MessageQueue(fromServer); this.fromPlugin = new MessageQueue(fromPlugin); this.fromServerResponses = Maps . newConcurrentMap(); Path logDir = Paths.get(System.getProperty(PLUGIN_HOME_JVM_PROPERTY) + File.separator + "log"); logDir.toFile().mkdirs(); this.log = Logger.builder().name(this.getClass().getName()) .level(getConfig().getLogLevel()).directory(logDir.toString()) .build(); // Redirect System.out and System.err to a console.log file Path consoleLog = logDir.resolve("console.log"); try { File consoleLogFile = consoleLog.toFile(); consoleLogFile.createNewFile(); FileOutputStream fos = new FileOutputStream(consoleLogFile); TeeOutputStream out = new TeeOutputStream(System.out, fos); TeeOutputStream err = new TeeOutputStream(System.err, fos); PrintStream consoleOut = new PrintStream(out, true); PrintStream consoleErr = new PrintStream(err, true); System.setOut(consoleOut); System.setErr(consoleErr); Runtime.getRuntime().addShutdownHook(new Thread(() -> { consoleOut.close(); consoleErr.close(); })); } catch (IOException e) { throw CheckedExceptions.throwAsRuntimeException(e); } } /** * Return a {@link BackgroundInformation} instance that has plugin-related * attributes that are needed for making background requests to the upstream * service. * * @return the Plugin's {@link BackgroundInformation}. */ public BackgroundInformation backgroundInformation() { return new BackgroundInformation(); } /** * Start the plugin and process requests until instructed to * {@link Instruction#STOP stop}. */ public void run() { setReadyState(); log.info("Running plugin {}", this.getClass()); ByteBuffer data; while ((data = fromServer.read()) != null) { RemoteMessage message = serializer.deserialize(data); if(message.type() == RemoteMessage.Type.REQUEST) { RemoteMethodRequest request = (RemoteMethodRequest) message; log.debug("Received REQUEST from Concourse Server: {}", message); Thread worker = new RemoteInvocationThread(request, fromPlugin, this, false, fromServerResponses); worker.setUncaughtExceptionHandler((thread, throwable) -> { log.error( "While processing request '{}', the following " + "non-recoverable error occurred:", request, throwable); }); worker.start(); } else if(message.type() == RemoteMessage.Type.RESPONSE) { RemoteMethodResponse response = (RemoteMethodResponse) message; log.debug("Received RESPONSE from Concourse Server: {}", response); ConcurrentMaps.putAndSignal(fromServerResponses, response.creds, response); } else if(message.type() == RemoteMessage.Type.STOP) { // STOP log.info("Stopping plugin {}", this.getClass()); break; } else { // Ignore the message... continue; } } } /** * Return the {@link PluginConfiguration preferences} for this plugin. *

* The plugin should override this class if the * {@link StandardPluginConfiguration} is insufficient. *

* * @return the {@link PluginConfiguration preferences} for the plugin */ protected PluginConfiguration getConfig() { return new StandardPluginConfiguration(); } /** * Signal that the plugin is ready for operations. */ private void setReadyState() { if(!inReadyState) { try { File ready = Files .getHashedFilePath(System .getProperty(PLUGIN_SERVICE_TOKEN_JVM_PROPERTY)) .toFile(); ready.getParentFile().mkdirs(); ready.createNewFile(); inReadyState = true; } catch (IOException e) {} } } /** * A wrapper class for all the information needed to perform background * requests in this Plugin and its related classes. * * @author Jeff Nelson */ @Immutable public class BackgroundInformation { private BackgroundInformation() {/* no-op */} /** * Return the {@link InterProcessCommunication} channel that the Plugin * and its related classes use for outgoing messages to the upstream * service. * * @return the outgoing channel */ public InterProcessCommunication outgoing() { return fromPlugin; } /** * Return the queue of responses from the upstream service. * * @return the response queue */ public ConcurrentMap responses() { return fromServerResponses; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy