package com.metrink.croquet; import java.io.File; import java.io.IOException; import java.io.Serializable; import javax.persistence.Entity; import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession; import org.apache.wicket.markup.html.WebPage; import org.apache.wicket.request.resource.IResource; import org.hibernate.dialect.Dialect; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonParseException; import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.metrink.croquet.LoggingSettings.LogFile; import com.metrink.croquet.logging.CroquetLoggingFactory; import com.metrink.croquet.wicket.CroquetApplication; /** * Class used to build the immutable Croquet instance. * @param <T> the type of the Settings class. */ public class CroquetWicketBuilder<T extends WicketSettings> { private static final Logger LOG = LoggerFactory.getLogger(CroquetWicketBuilder.class); private final T settings; private final Class<T> settingsClass; private CroquetWicketBuilder(final Class<T> settingsClass, final T settings) { this.settingsClass = settingsClass; this.settings = settings; } /** * Creates an instance of the {@link CroquetWicketBuilder} that uses the default {@link WicketSettings} class. * @param args the command line args. * @return an instance of the {@link CroquetWicketBuilder}. */ public static CroquetWicketBuilder<WicketSettings> create(final String[] args) { return CroquetWicketBuilder.create(WicketSettings.class, args); } /** * Creates an instance of the {@link CroquetWicketBuilder} given the command line args. * @param settingsClass the Class of the Settings class. * @param args the command line args. * @return an instance of the {@link CroquetWicketBuilder}. * @param <S> the type of the settings class. */ public static <S extends WicketSettings> CroquetWicketBuilder<S> create(final Class<S> settingsClass, final String[] args) { if (args.length < 1) { System.err.println("Configuration YAML file not provided"); System.exit(-1); } // parse the arguments and configuration file here final String filename = args[0]; final ObjectMapper mapper = new ObjectMapper(new YAMLFactory()); final S configuration; try { configuration = mapper.readValue(new File(filename), settingsClass); configuration.initialize(); } catch (final JsonParseException e) { LOG.error("JsonParseException: {}", e.getMessage()); throw errorAndDie(filename, e.getMessage()); } catch (final JsonMappingException e) { LOG.error("JsonMappingException: {}", e.getMessage()); throw errorAndDie(filename, e.getMessage()); } catch (final IOException e) { LOG.error("IOException: {}", e.getMessage()); throw errorAndDie(filename, e.getMessage()); } new CroquetLoggingFactory().configureLogging(configuration.getLoggingSettings()); return new CroquetWicketBuilder<S>(settingsClass, configuration); } private static IllegalStateException errorAndDie(final String filename, final String reason) { System.err.println("Unable to load '" + filename + "' because: " + reason); System.exit(-1); // The only reason this returns an exception is so that we can throw on the same line as we call. Otherwise, // the constructor will complain about the uninitialized settings field. return new IllegalStateException("Unreachable"); } /** * Builds the {@link CroquetWicket} object. * @return the newly built {@link CroquetWicket} object. */ public CroquetWicket<T> build() { checkDbSettings(); checkLoggingSettings(); return new CroquetWicket<T>(settingsClass, settings); } /** * Builds a {@link CroquetTester} object. * @return the newly built {@link CroquetTester} object. */ public CroquetTester<T> buildTester() { checkDbSettings(); checkLoggingSettings(); return new CroquetTester<T>(settingsClass, settings); } /** * Runs checks over the database settings before building a {@link CroquetWicket} instance. */ protected void checkDbSettings() { final DatabaseSettings db = settings.getDatabaseSettings(); if(db == null) { throw new RuntimeException("You must specify db settings." + " If you don't need a database, simply leave the section blank."); } // CHECKSTYLE:OFF conditional complexity too large if(db.getPersistenceUnit() != null && (db.getDriver() != null || db.getDialectClass() != null || db.getJdbcUrl() != null || db.getUser() != null || db.getPass() != null)) { // CHECKSTYLE:ON throw new IllegalStateException("Cannot set persist-unit with any other database settings"); } } /** * Runs some checks over the log settings before building a {@link Corquet} instance. */ protected void checkLoggingSettings() { final LoggingSettings logSettings = settings.getLoggingSettings(); final LogFile logFileSettings = logSettings.getLogFile(); if(logFileSettings.getCurrentLogFilename() != null && logFileSettings.isEnabled() == false) { LOG.warn("You specified a log file, but have it disabled"); } if(logFileSettings.isEnabled() && logFileSettings.getCurrentLogFilename() == null) { throw new IllegalStateException("You enabled logging to a file, but didn't specify the file name"); } } /** * Sets the class of the Wicket application. * @param application the WebApplication class for Wicket. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> setWebApplicationClass(final Class<? extends CroquetApplication> application) { settings.setWebApplicationClass(application); return this; } /** * Sets the {@link AbstractAuthenticatedWebSession} for the application. * @param wicketSessionClass the {@link AbstractAuthenticatedWebSession} class. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> setWicketSessionClass(final Class<? extends AbstractAuthenticatedWebSession> wicketSessionClass) { settings.setWicketSessionClass(wicketSessionClass); return this; } /** * Sets the class to use as the home page. * @param homePage the {@link WebPage} to use as the home page. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> setHomePageClass(final Class<? extends WebPage> homePage) { settings.setHomePageClass(homePage); return this; } /** * Sets the class to use as the login page. * @param loginPage the {@link WebPage} to use as the login page. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> setLoginPageClass(final Class<? extends WebPage> loginPage) { settings.setLoginPageClass(loginPage); return this; } /** * Sets the class to use as the exception page. * @param exceptionPage the {@link WebPage} to use as the exception page. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> setExceptionPageClass(final Class<? extends WebPage> exceptionPage) { settings.setExceptionPageClass(exceptionPage); return this; } /** * Adds a {@link WebPage} to the list of mounts. * @param path the location to mount the page. * @param page the page to mount. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> addPageMount(final String path, final Class<? extends WebPage> page) { settings.addPageMount(path, page); return this; } /** * Adds a health check resource to a particular mount. * @param path the path * @param resource the resource to mount * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> addHealthCheck(final String path, final Class<? extends IResource> resource) { settings.addResourceMount(path, resource); return this; } /** * Adds a {@link IResource} to the list of resources. * @param path the location to mount the page. * @param resource the resource to mount. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> addResource(final String path, final Class<? extends IResource> resource) { settings.addResourceMount(path, resource); return this; } /** * Adds a JPA entity to Croquet. * @param entity the entity to add. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> addJpaEntity(final Class<? extends Serializable> entity) { // check to ensure the class has the @Entity annotation if(entity.getAnnotation(Entity.class) == null) { throw new IllegalArgumentException("Only classes marked with @Entity can be added"); } settings.getDatabaseSettings().addEntity(entity); return this; } /** * Adds a property to the database configuration. * * <b>Only used when configuring the DB via the YAML file.</b> * @param property the DB property to set. * @param value the value of the property. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> addDbProperty(final String property, final Object value) { settings.getDatabaseSettings().addProperty(property, value); return this; } /** * Sets the hibernate.dialect class. * @param dialectClass the dialect class to use. * @return the {@link CroquetWicketBuilder}. */ public CroquetWicketBuilder<T> setSqlDialect(final Class<? extends Dialect> dialectClass) { settings.getDatabaseSettings().setDialectClass(dialectClass); return this; } /** * Sets the name of the PID file to drop on Linux. * * @param pidFilename the pid filename * @return the {@link CroquetWicketBuilder} */ public CroquetWicketBuilder<T> setPidFile(final String pidFilename) { settings.setPidFile(pidFilename); return this; } }