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

com.speedment.runtime.core.internal.AbstractApplicationBuilder Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

There is a newer version: 3.1.18
Show newest version
/**
 *
 * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
 *
 * 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.speedment.runtime.core.internal;

import com.speedment.common.injector.InjectBundle;
import com.speedment.common.injector.Injector;
import com.speedment.common.injector.InjectorBuilder;
import com.speedment.common.injector.exception.CyclicReferenceException;
import static com.speedment.common.injector.execution.ExecutionBuilder.resolved;
import static com.speedment.common.injector.execution.ExecutionBuilder.started;
import static com.speedment.common.invariant.NullUtil.requireNonNulls;
import com.speedment.common.logger.Level;
import com.speedment.common.logger.Logger;
import com.speedment.common.logger.LoggerManager;
import com.speedment.runtime.config.Dbms;
import com.speedment.runtime.config.Document;
import com.speedment.runtime.config.Project;
import com.speedment.runtime.config.Schema;
import com.speedment.runtime.config.trait.HasEnabled;
import com.speedment.runtime.config.trait.HasName;
import com.speedment.runtime.config.util.DocumentDbUtil;
import com.speedment.runtime.config.util.DocumentUtil;
import static com.speedment.runtime.config.util.DocumentUtil.Name.DATABASE_NAME;
import com.speedment.runtime.core.ApplicationBuilder;
import com.speedment.runtime.core.ApplicationMetadata;
import com.speedment.runtime.core.RuntimeBundle;
import com.speedment.runtime.core.Speedment;
import com.speedment.runtime.core.component.DbmsHandlerComponent;
import com.speedment.runtime.core.component.InfoComponent;
import com.speedment.runtime.core.component.PasswordComponent;
import com.speedment.runtime.core.component.ProjectComponent;
import com.speedment.runtime.core.db.DbmsMetadataHandler;
import com.speedment.runtime.core.db.DbmsType;
import com.speedment.runtime.core.exception.SpeedmentException;
import com.speedment.runtime.core.manager.Manager;
import com.speedment.runtime.core.util.DatabaseUtil;
import java.sql.SQLException;
import static java.util.Objects.requireNonNull;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * This abstract class is implemented by classes that can build a
 * {@link Speedment} application.
 *
 * @param  the type that is being built
 * @param  the (self) type of the AbstractApplicationBuilder
 *
 * @author Per Minborg
 * @author Emil Forslund
 * @since 2.0.0
 */
public abstract class AbstractApplicationBuilder<
        APP extends Speedment, 
        BUILDER extends AbstractApplicationBuilder
> implements ApplicationBuilder {

    private final static Logger LOGGER = LoggerManager.getLogger(
        LogType.APPLICATION_BUILDER.getLoggerName());
    
    private final InjectorBuilder injectorBuilder;

    private boolean skipCheckDatabaseConnectivity;
    private boolean skipValidateRuntimeConfig;
    private boolean skipLogoPrintout;
    
    protected AbstractApplicationBuilder(
        Class applicationImplClass,
        Class metadataClass) {

        this(Injector.builder()
            .withBundle(RuntimeBundle.class)
            .withComponent(applicationImplClass)
            .withComponent(metadataClass)
        );
    }
    
    protected AbstractApplicationBuilder(
        ClassLoader classLoader,
        Class applicationImplClass,
        Class metadataClass) {

        this(Injector.builder(classLoader)
            .withBundle(RuntimeBundle.class)
            .withComponent(applicationImplClass)
            .withComponent(metadataClass)
        );
    }

    protected AbstractApplicationBuilder(InjectorBuilder injectorBuilder) {
        this.injectorBuilder = requireNonNull(injectorBuilder);
        this.skipCheckDatabaseConnectivity = false;
        this.skipValidateRuntimeConfig     = false;
    }

    @Override
    public  BUILDER with(
            Class type, String name, BiConsumer consumer) {
        
        requireNonNulls(type, name, consumer);

        injectorBuilder.before(resolved(ProjectComponent.class)
            .withStateInitialized(Injector.class)
            .withExecute((projComp, injector) -> 
                DocumentDbUtil.traverseOver(projComp.getProject(), type)
                    .filter(doc -> DocumentUtil.relativeName(
                        HasName.of(doc), 
                        Dbms.class, 
                        DATABASE_NAME
                    ).equals(name))
                    .forEach(doc -> consumer.accept(injector, doc))
            )
        );
        
        return self();
    }

    @Override
    public  BUILDER with(
            Class type, BiConsumer consumer) {
        
        requireNonNulls(type, consumer);
        
        injectorBuilder.before(resolved(ProjectComponent.class)
            .withStateInitialized(Injector.class)
            .withExecute((projComp, injector) -> 
                DocumentDbUtil.traverseOver(projComp.getProject(), type)
                    .forEach(doc -> consumer.accept(injector, doc))
            )
        );
        
        return self();
    }

    @Override
    public  BUILDER with(
            Class type, Consumer consumer) {
        
        injectorBuilder.before(resolved(ProjectComponent.class)
            .withExecute(projComp -> 
                DocumentDbUtil.traverseOver(projComp.getProject(), type)
                    .forEach(consumer)
            )
        );
        
        return self();
    }

    @Override
    public BUILDER withParam(String key, String value) {
        requireNonNulls(key, value);
        injectorBuilder.withParam(key, value);
        return self();
    }

    @Override
    public BUILDER withPassword(char[] password) {
        // password nullable
        injectorBuilder.before(started(PasswordComponent.class)
            .withStateResolved(ProjectComponent.class)
            .withExecute((passComp, projComp) -> 
                projComp.getProject().dbmses().forEach(
                    dbms -> passComp.put(dbms, password)
                )
            )
        );
        
        return self();
    }

    @Override
    public BUILDER withPassword(String dbmsName, char[] password) {
        requireNonNull(dbmsName);
        // password nullable
        
        injectorBuilder.before(started(PasswordComponent.class)
            .withExecute(passComp -> passComp.put(dbmsName, password))
        );
        
        return self();
    }

    @Override
    public BUILDER withPassword(String password) {
        // password nullable
        return withPassword(password == null ? null : password.toCharArray());
    }

    @Override
    public BUILDER withPassword(String dbmsName, String password) {
        requireNonNull(dbmsName);
        // password nullable
        return withPassword(dbmsName, 
            password == null ? null : password.toCharArray()
        );
    }

    @Override
    public BUILDER withUsername(String username) {
        // username nullable
        with(Dbms.class, dbms -> dbms.mutator().setUsername(username));
        return self();
    }

    @Override
    public BUILDER withUsername(String dbmsName, String username) {
        requireNonNull(dbmsName);
        // username nullable
        with(Dbms.class, dbmsName, d -> d.mutator().setUsername(username));
        return self();
    }

    @Override
    public BUILDER withIpAddress(String ipAddress) {
        requireNonNull(ipAddress);
        with(Dbms.class, d -> d.mutator().setIpAddress(ipAddress));
        return self();
    }

    @Override
    public BUILDER withIpAddress(String dbmsName, String ipAddress) {
        requireNonNulls(dbmsName, ipAddress);
        with(Dbms.class, dbmsName, d -> d.mutator().setIpAddress(ipAddress));
        return self();
    }

    @Override
    public BUILDER withPort(int port) {
        with(Dbms.class, d -> d.mutator().setPort(port));
        return self();
    }

    @Override
    public BUILDER withPort(String dbmsName, int port) {
        requireNonNull(dbmsName);
        with(Dbms.class, dbmsName, d -> d.mutator().setPort(port));
        return self();
    }

    @Override
    public BUILDER withSchema(String schemaName) {
        requireNonNull(schemaName);
        with(Schema.class, s -> s.mutator().setName(schemaName));
        return self();
    }

    @Override
    public BUILDER withSchema(String oldSchemaName, String schemaName) {
        requireNonNulls(oldSchemaName, schemaName);
        with(Schema.class, oldSchemaName, s -> s.mutator().setName(schemaName));
        return self();
    }

    @Override
    public BUILDER withConnectionUrl(String connectionUrl) {
        with(Dbms.class, d -> d.mutator().setConnectionUrl(connectionUrl));
        return self();
    }

    @Override
    public BUILDER withConnectionUrl(String dbmsName, String connectionUrl) {
        requireNonNull(dbmsName);
        with(Dbms.class, dbmsName, s -> s.mutator().setName(connectionUrl));
        return self();
    }

    @Override
    public > BUILDER withManager(Class managerImplType) {
        requireNonNull(managerImplType);
        withInjectable(injectorBuilder, managerImplType);
        return self();
    }

    @Override
    public BUILDER withSkipCheckDatabaseConnectivity() {
        this.skipCheckDatabaseConnectivity = true;
        return self();
    }

    @Override
    public BUILDER withSkipValidateRuntimeConfig() {
        this.skipValidateRuntimeConfig = true;
        return self();
    }

    @Override
    public BUILDER withSkipLogoPrintout() {
        this.skipLogoPrintout = true;
        return self();
    }

    @Override
    public BUILDER withBundle(Class bundleClass) {
        requireNonNull(bundleClass);
        injectorBuilder.withBundle(bundleClass);
        return self();
    }

    @Override
    public BUILDER withComponent(Class injectableClass) {
        requireNonNull(injectableClass);
        injectorBuilder.withComponent(injectableClass);
        return self();
    }

    @Override
    @Deprecated
    public BUILDER withComponent(String key, Class injectableClass) {
        requireNonNulls(key, injectableClass);
        injectorBuilder.withComponent(injectableClass);
        return self();
    }

    @Override
    public BUILDER withLogging(HasLogglerName namer) {
        LoggerManager.getLogger(namer.getLoggerName()).setLevel(Level.DEBUG);
        
        if (LogType.APPLICATION_BUILDER.getLoggerName()
                .equals(namer.getLoggerName())) {
            
            // Special case becaues its in a common module
            Injector.logger().setLevel(Level.DEBUG); 
            InjectorBuilder.logger().setLevel(Level.DEBUG);
        }
        
        return self();
    }

    @Override
    public final APP build() {
        final Injector inj;

        try {
            inj = injectorBuilder.build();
        } catch (final InstantiationException | CyclicReferenceException ex) {
            throw new SpeedmentException("Error in dependency injection.", ex);
        }

        printWelcomeMessage(inj);

        if (!skipValidateRuntimeConfig) {
            validateRuntimeConfig(inj);
        }
        if (!skipCheckDatabaseConnectivity) {
            checkDatabaseConnectivity(inj);
        }

        return build(inj);
    }

    /**
     * Builds the application using the specified injector.
     *
     * @param injector the injector to use
     * @return the built instance.
     */
    protected abstract APP build(Injector injector);

    protected void validateRuntimeConfig(Injector injector) {
        LOGGER.debug("Validating Runtime Configuration");
        final Project project = injector.getOrThrow(ProjectComponent.class)
            .getProject();
        
        if (project == null) {
            throw new SpeedmentException("No project defined");
        }

        project.dbmses().forEach(d -> {
            final String typeName = d.getTypeName();
            final Optional oDbmsType = injector.getOrThrow(
                DbmsHandlerComponent.class).findByName(typeName);
            
            if (!oDbmsType.isPresent()) {
                throw new SpeedmentException("The database type " + typeName + 
                    " is not registered with the " + 
                    DbmsHandlerComponent.class.getSimpleName());
            }
            final DbmsType dbmsType = oDbmsType.get();
            
            if (!dbmsType.isSupported()) {
                LOGGER.error("The database driver class " + dbmsType.getDriverName() + 
                    " is not available. Make sure to include it in your " + 
                    "class path (e.g. in the POM file)"
                );
            }
        });

    }

    protected void checkDatabaseConnectivity(Injector injector) {
        LOGGER.debug("Checking Database Connectivity");
        final Project project = injector.getOrThrow(ProjectComponent.class)
            .getProject();
        
        project.dbmses().forEachOrdered(dbms -> {

            final DbmsHandlerComponent dbmsHandlerComponent = injector
                .getOrThrow(DbmsHandlerComponent.class);
            
            final DbmsType dbmsType = DatabaseUtil
                .dbmsTypeOf(dbmsHandlerComponent, dbms);
            
            final DbmsMetadataHandler handler = dbmsType.getMetadataHandler();

            try {
                LOGGER.info(handler.getDbmsInfoString(dbms));
            } catch (final SQLException sqle) {
                throw new SpeedmentException("Unable to establish initial " + 
                    "connection with the database named " + 
                    dbms.getName() + ".", sqle);
            }
        });
    }

    /**
     * Prints a welcome message to the output channel.
     *
     * @param injector the injector to use
     */
    protected void printWelcomeMessage(Injector injector) {

        final InfoComponent info = injector.getOrThrow(InfoComponent.class);
        final String title = info.getTitle();
        final String version = info.getImplementationVersion();

        if (!skipLogoPrintout) {
            final String speedmentMsg = "\n"
                + "   ____                   _                     _     \n"
                + "  / ___'_ __  __  __   __| |_ __ __    __ _ __ | |    \n"
                + "  \\___ | '_ |/  \\/  \\ / _  | '_ \\ _ \\ /  \\ '_ \\| |_   \n"
                + "   ___)| |_)| '_/ '_/| (_| | | | | | | '_/ | | |  _|  \n"
                + "  |____| .__|\\__\\\\__\\ \\____|_| |_| |_|\\__\\_| |_| '_   \n"
                + "=======|_|======================================\\__|==\n"
                + "   :: " + title + " by " + info.getVendor()
                + ":: (v" + version + ") \n";

            LOGGER.info(speedmentMsg);
        }
        final String msg = title + " (" + info.getSubtitle()
            + ") version " + version
            + " by " + info.getVendor()
            + " Specification version "
            + info.getSpecificationVersion();
        LOGGER.info(msg);

        if (!info.isProductionMode()) {
            LOGGER.warn("This version is NOT INTENDED FOR PRODUCTION USE!");
        }

        try {
            final Package package_ = Runtime.class.getPackage();
            final String javaMsg = package_.getSpecificationTitle()
                + " " + package_.getSpecificationVersion()
                + " by " + package_.getSpecificationVendor()
                + ". Implementation "
                + package_.getImplementationVendor()
                + " " + package_.getImplementationVersion()
                + " by " + package_.getImplementationVendor();
            LOGGER.info(javaMsg);

            final String versionString = package_.getImplementationVersion();
            final Optional isVersionOk = isVersionOk(versionString);
            if (isVersionOk.isPresent()) {
                if (!isVersionOk.get()) {
                    LOGGER.warn("The current Java version (" + versionString + 
                        ") is outdated. Please upgrade to a more recent " + 
                        "Java version.");
                }
            } else {
                LOGGER.warn("Unable to fully parse the java version. " + 
                    "Version check skipped!");
            }
        } catch (final Exception ex) {
            LOGGER.info("Unknown Java version.");
        }

    }

    private BUILDER self() {
        @SuppressWarnings("unchecked")
        final BUILDER builder = (BUILDER) this;
        return builder;
    }

    Optional isVersionOk(String versionString) {
        final Pattern pattern = Pattern.compile("(\\d+)[\\\\.](\\d+)[\\\\.](\\d+)_(\\d+)");
        final Matcher matcher = pattern.matcher(versionString);

        if (matcher.find() && matcher.groupCount() >= 4) {
            final String majorVersionString = matcher.group(1);
            final String minorVersionString = matcher.group(2);
            final String patchVersionString = matcher.group(3);
            final String microVersionString = matcher.group(4);
            try {
                final int majorVersion = Integer.parseInt(majorVersionString);
                final int minorVersion = Integer.parseInt(minorVersionString);
                final int patchVersion = Integer.parseInt(patchVersionString);
                final int microVersion = Integer.parseInt(microVersionString);
                boolean ok = true;
                if (majorVersion < 1) {
                    ok = false;
                }
                if (majorVersion == 1) {
                    if (minorVersion < 8) {
                        ok = false;
                    }
                    if (minorVersion == 8) {
                        if (patchVersion < 0) {
                            ok = false;
                        }
                        if (patchVersion == 0) {
                            if (microVersion < 40) {
                                ok = false;
                            }
                        }
                    }
                }
                return Optional.of(ok);
            } catch (NumberFormatException nfe) {
                return Optional.empty();
            }
        } else {
            return Optional.empty();
        }
    }

    private static  void withInjectable(
            InjectorBuilder injector, 
            Class injectableImplType) {
        
        requireNonNull(injectableImplType);
        injector.withComponent(injectableImplType);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy