package io.eguan.vold.adm;
/*
* #%L
* Project eguan
* %%
* Copyright (C) 2012 - 2017 Oodrive
* %%
* 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.
* #L%
*/
import io.eguan.configuration.ConfigValidationException;
import io.eguan.configuration.MetaConfiguration;
import io.eguan.configuration.ValidationError;
import io.eguan.rest.container.JettyConfigurationContext;
import io.eguan.rest.container.ServletServer;
import io.eguan.rest.jaxrs.JaxRsAppContext;
import io.eguan.rest.jaxrs.JaxRsConfiguredApplication;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Launcher for the integrated REST runtime.
*
* @author oodrive
* @author pwehrle
* @author ebredzinski
*
*/
@Immutable
public final class RestLauncher {
private static final Logger LOGGER = LoggerFactory.getLogger(RestLauncher.class);
/**
* Configuration resource name to load as default.
*/
private static final String DEFAULT_CONFIG_RESOURCE = "/" + RestLauncher.class.getSimpleName() + ".properties";
/**
* Context to load as default from internal resources.
*/
private static final String DEFAULT_CONTEXT_RESOURCE = "/vold-adm.xml";
/**
* Command line usage message.
*/
private static final String USAGE = String.format(
"usage : java %s [<configuration file> <context initialization file>]",
RestLauncher.class.getCanonicalName());
/**
* Lock guarding the initialized state, i.e. {@link RestLauncher#initialized} and {@link #servletServer}.
*/
private final Object initLock = new Object();
/**
* Initialized state field.
*/
@GuardedBy("initLock")
private volatile boolean initialized = false;
/**
* Runtime servlet server instance.
*/
@GuardedBy("initLock")
private ServletServer servletServer;
/**
* Reference to the configuration input resource to load on {@link #init()}.
*/
private final InputStream configInput;
/**
* Reference to the context input resource to load on {@link #init()}.
*/
private final InputStream contextInput;
/**
* Constructs an instance initialized from default configuration and context resources.
*
* @throws IllegalStateException
* if any of the default resources cannot be found or referenced
*/
public RestLauncher() throws IllegalStateException {
final Class<? extends RestLauncher> clazz = getClass();
this.configInput = clazz.getResourceAsStream(DEFAULT_CONFIG_RESOURCE);
this.contextInput = clazz.getResourceAsStream(DEFAULT_CONTEXT_RESOURCE);
}
/**
* Constructs an instance from existing configuration and context files.
*
* @param configInput
* a {@link URI} referencing the file from which to read a {@link MetaConfiguration} with a
* {@link JettyConfigurationContext}
* @param contextInput
* a {@link URI} referencing a file in Jetty XML format configuring a {@link JaxRsAppContext} instance
*/
public RestLauncher(@Nonnull final InputStream configInput, @Nonnull final InputStream contextInput) {
this.configInput = Objects.requireNonNull(configInput);
this.contextInput = Objects.requireNonNull(contextInput);
}
/**
* Initializes the launcher.
*
* @throws IllegalStateException
* if this instance cannot be initialized
*/
public final void init() throws IllegalStateException {
synchronized (initLock) {
if (initialized) {
LOGGER.warn("Already initialized");
return;
}
try {
final MetaConfiguration configuration = MetaConfiguration.newConfiguration(configInput,
JettyConfigurationContext.getInstance());
final JaxRsConfiguredApplication jerseyApplication = new JaxRsConfiguredApplication(contextInput);
servletServer = new ServletServer(configuration, jerseyApplication);
servletServer.init();
initialized = true;
}
catch (NullPointerException | IllegalArgumentException | IOException e) {
LOGGER.error("Exception on initialization", e);
throw new IllegalStateException(e);
}
catch (final ConfigValidationException ce) {
final StringBuilder errMsg = new StringBuilder();
for (final ValidationError currError : ce.getValidationReport()) {
errMsg.append(ValidationError.getFormattedErrorReport(currError));
}
LOGGER.error("Configuration exception on initialization: " + errMsg.toString(), ce);
throw new IllegalStateException(ce);
}
}
}
/**
* Returns the initialization state as a result from calls to {@link #init()} and {@link #fini()}.
*
* @return the initialization state
*/
public final boolean isInitialized() {
return initialized;
}
/**
* Resets this instance to an uninitialized state.
*
* Calling this on an {@link #isInitialized() initialized} instance does nothing.
*/
public final void fini() {
synchronized (initLock) {
if (!initialized) {
LOGGER.warn("Not initialized");
return;
}
servletServer.fini();
servletServer = null;
initialized = false;
}
}
/**
* Starts the launcher.
*
* @throws IllegalStateException
* if the launcher is not {@link #isInitialized() initialized} or starting it fails
*/
public final void start() throws IllegalStateException {
synchronized (initLock) {
if (!initialized) {
throw new IllegalStateException("Not initialized");
}
try {
servletServer.start();
}
catch (final Exception e) {
LOGGER.error("Exception on start", e);
// TODO: filter exceptions
throw new IllegalStateException(e);
}
}
}
/**
* Returns the running state of the server.
*
* In an {@link #isInitialized() uninitialized state}, this returns <code>false</code>.
*
* @return <code>true</code> if the server is starting or started, <code>false</code> otherwise
*/
public boolean isStarted() {
synchronized (initLock) {
return initialized && servletServer.isStarted();
}
}
/**
* Stops the server.
*
* Does nothing if the server is not initialized.
*
* @throws IllegalStateException
* if stopping the server fails
*/
public final void stop() throws IllegalStateException {
synchronized (initLock) {
if (!initialized) {
return;
}
try {
servletServer.stop();
}
catch (final Exception e) {
LOGGER.error("Exception on start", e);
// TODO: filter exceptions
throw new IllegalStateException(e);
}
}
}
/**
* Blocks until the launcher is {@link #stop() stopped}.
*
* Returns immediately if the launcher is already stopped.
*
* @throws InterruptedException
* if the launcher's execution is abnormally terminated
* @throws IllegalStateException
* if the launcher is not {@link #isInitialized() initialized}
*/
public final void join() throws InterruptedException {
if (!initialized) {
throw new IllegalStateException("Not initialized");
}
servletServer.join();
}
/**
* Launches the configured server with optional configuration and context files.
*
* @param args
* command line parameters, see {@link #USAGE}
*/
public static void main(final String[] args) {
if ((args.length != 0) && (args.length != 2)) {
System.out.println(USAGE);
System.exit(1);
}
try {
final RestLauncher launcher;
if (args.length == 2) {
final FileInputStream configInputStream = new FileInputStream(args[0]);
final FileInputStream contextInputStream = new FileInputStream(args[1]);
launcher = new RestLauncher(configInputStream, contextInputStream);
}
else {
launcher = new RestLauncher();
}
launcher.init();
try {
launcher.start();
try {
launcher.join();
}
finally {
launcher.stop();
}
}
finally {
launcher.fini();
}
}
catch (final Throwable e) {
LOGGER.error("REST server launch failed", e);
System.exit(2);
}
}
}