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

com.google.code.play2.plugin.Play2RunMojo Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2013-2020 Grzegorz Slowikowski (gslowikowski at gmail dot com)
 *
 * 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.google.code.play2.plugin;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.ServiceLoader;
import java.util.Set;

import org.apache.maven.artifact.Artifact;
import org.apache.maven.artifact.resolver.ArtifactResolutionException;
import org.apache.maven.lifecycle.LifecycleExecutor;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Component;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.project.MavenProject;

import org.codehaus.plexus.PlexusConstants;
import org.codehaus.plexus.PlexusContainer;
import org.codehaus.plexus.context.Context;
import org.codehaus.plexus.context.ContextException;
import org.codehaus.plexus.personality.plexus.lifecycle.phase.Contextualizable;

import com.google.code.play2.provider.api.Play2Builder;
import com.google.code.play2.provider.api.Play2DevServer;
import com.google.code.play2.provider.api.Play2Provider;
import com.google.code.play2.provider.api.Play2Runner;
import com.google.code.play2.provider.api.Play2RunnerConfiguration;
import com.google.code.play2.provider.api.Play2TemplateCompiler;

import com.google.code.sbt.compiler.api.AnalysisProcessor;

import com.google.code.play2.watcher.api.FileWatchService;
import com.google.code.play2.watcher.api.FileWatchServices;

/**
 * Run Play! server in development mode ({@code sbt run} equivalent).
 * 
 * @author Grzegorz Slowikowski
 * @since 1.0.0
 */
@Mojo( name = "run", requiresDependencyCollection = ResolutionScope.RUNTIME )
public class Play2RunMojo
    extends AbstractPlay2EnhanceMojo implements Contextualizable
{
    /**
     * Scala artifacts "groupId".
     */
    private static final String SCALA_GROUPID = "org.scala-lang";

    /**
     * Scala library "artifactId".
     */
    private static final String SCALA_LIBRARY_ARTIFACTID = "scala-library";

    /**
     * Default HTTP port.
     */
    private static final int DEFAULT_HTTP_PORT = 9000;

    /**
     * Default HTTP address (not supported by all providers).
     */
    private static final String DEFAULT_HTTP_ADDRESS = "0.0.0.0";

    /**
     * Source files encoding.
     * 
*
* If not specified, the encoding value will be the value of the {@code file.encoding} system property. *
* * @since 1.0.0 */ @Parameter( property = "project.build.sourceEncoding" ) protected String sourceEncoding; /** * Allows the run to be skipped. *
* * @since 1.0.0 */ @Parameter( property = "play2.runSkip", defaultValue = "false" ) private boolean runSkip; /** * Directory containing all processed web assets. *
* * @since 1.0.0 */ @Parameter( property = "play2.assetsOutputDirectory", defaultValue = "${project.build.outputDirectory}/public" ) //TODO required or not? private File assetsOutputDirectory; /** * Web asset URLs prefix. *
* * @since 1.0.0 */ @Parameter( property = "play2.assetsPrefix", defaultValue = "public/" ) //TODO required or not? private String assetsPrefix; /** * Maven goals to execute during project rebuild. *
*
* In multi-module projects they are executed for all modules being rebuilt. *
* * @since 1.0.0 */ @Parameter( property = "play2.runGoals", defaultValue = "process-classes", required = true ) private String runGoals; /** * Additional Maven goals to execute during project rebuild. *
*
* In multi-module projects they are executed only for the main module. *
*
* It's required when calling SbtWeb plugin (via sbtrun-maven-plugin) * in multi-module project. *
* * @since 1.0.0 */ @Parameter( property = "play2.runAdditionalGoals", defaultValue = "" ) private String runAdditionalGoals; /** * Additional JVM arguments passed to Play! server's JVM. *
*
* Because this goal does not fork JVM, only system properties are used, other arguments are ignored. *
* * @since 1.0.0 */ @Parameter( property = "play2.serverJvmArgs", defaultValue = "" ) private String serverJvmArgs; /** * Server port (HTTP protocol) or {@code disabled} to disable HTTP protocol. *
* * @since 1.0.0 */ @Parameter( property = "play2.httpPort", defaultValue = "" ) private String httpPort; /** * Server port for secure connection (HTTPS protocol). *
* * @since 1.0.0 */ @Parameter( property = "play2.httpsPort", defaultValue = "" ) private String httpsPort; /** * Server address. *
* * @since 1.0.0 */ @Parameter( property = "play2.httpAddress", defaultValue = "" ) private String httpAddress; /** * Extra settings used only in development mode * (see Play! Framework documentation) *
*
* Space-separated list of key=value pairs, e.g. *
* {@code play.server.http.port=9001 play.server.https.port=9443} *
* * @since 1.0.0 */ @Parameter( property = "play2.devSettings", defaultValue = "" ) private String devSettings; /** * Identifier of the module to run. *
*
* Important in multi-module projects with more than one {@code play2} modules * to select which one should be run. *
* There are three supported formats: *
    *
  • * {@code artifactId} or {@code :artifactId} - find first module with given {@code artifactId} *
  • *
  • * {@code groupId:artifactId} - find module with given {@code groupId} and {@code artifactId} *
  • *
* If not specified, first reactor module with {@code play2} packaging will be selected. *
* * @since 1.0.0 */ @Parameter( property = "play2.mainModule", defaultValue = "" ) private String mainModule; /** * Watch service used to watch for file changes. *
*
* Supported watch services: *
    *
  • * {@code jdk7} *
  • *
  • * {@code jnotify} *
  • *
  • * {@code polling} *
  • *
*
* Default watch service is selected based on operating system and JDK version. *
* * @since 1.0.0 */ @Parameter( property = "play2.fileWatchService", defaultValue = "" ) private String fileWatchService; /** * The component used to execute the second Maven execution. */ @Component private LifecycleExecutor lifecycleExecutor; /** * The plexus container. */ private PlexusContainer container; /** * Map of file watch service implementations. For now only zero or one allowed. */ @Component( role = FileWatchService.class ) private Map watchServices; /** * Retrieves the Plexus container. * @param context the context * @throws ContextException if the container cannot be retrieved. */ @Override public void contextualize( Context context ) throws ContextException { container = (PlexusContainer) context.get( PlexusConstants.PLEXUS_KEY ); } @Override protected void internalExecute() throws MojoExecutionException, MojoFailureException { if ( runSkip ) { getLog().info( "Skipping execution" ); return; } if ( mainModule != null && !"".equals( mainModule ) && !isMatchingProject( project, mainModule ) ) { getLog().debug( "Not main module - skipping execution" ); return; } File baseDir = project.getBasedir(); // Make separate method for checking conf file (use in "run" and "start" mojos) /*???File confDir = new File( baseDir, "conf" ); if ( !confDir.isDirectory() ) { getLog().debug( "No \"conf\" directory - skipping execution" ); return; } if ( !new File( confDir, "application.conf" ).isFile() && !new File( confDir, "application.json" ).isFile() ) { getLog().debug( "No \"conf/application.conf\" or \"conf/application.json\" file - skipping execution" ); return; }*/ Play2Provider play2Provider = getProvider(); Play2Runner play2Runner = play2Provider.getRunner(); if ( !play2Runner.supportsRunInDevMode() ) { getLog().warn( "Running in development mode not supported for this Play! Framework version" ); return; } List goals = Arrays.asList( runGoals.trim().split( " " ) ); List additionalGoals = runAdditionalGoals != null && !"".equals( runAdditionalGoals ) ? Arrays.asList( runAdditionalGoals.trim().split( " " ) ) : Collections.emptyList(); if ( !assetsPrefix.endsWith( "/" ) ) { assetsPrefix = assetsPrefix + "/"; } getLog().debug( "Required reactor modules:" ); List upstreamProjects = session.getProjectDependencyGraph().getUpstreamProjects( project, true ); List allRequiredReactorModules = new ArrayList( 1 + upstreamProjects.size() ); for ( MavenProject p: upstreamProjects ) { allRequiredReactorModules.add( p ); getLog().debug( "- " + p.getGroupId() + ":" + p.getArtifactId() ); } allRequiredReactorModules.add( project ); String scalaVersion = null; Set projectArtifacts = project.getArtifacts(); List dependencyClasspath = new ArrayList( projectArtifacts.size() ); for ( Artifact a: projectArtifacts ) { if ( !isReactorProject( upstreamProjects, a ) ) { if ( !a.isResolved() ) { try { getResolvedArtifact( a, false ); } catch ( ArtifactResolutionException e ) { throw new MojoExecutionException( "Artifact resolution failed", e ); } } dependencyClasspath.add( a.getFile() ); if ( SCALA_GROUPID.equals( a.getGroupId() ) && SCALA_LIBRARY_ARTIFACTID.equals( a.getArtifactId() ) ) { scalaVersion = a.getVersion(); } } } List outputDirectories = new ArrayList( allRequiredReactorModules.size() ); for ( MavenProject p: allRequiredReactorModules ) { outputDirectories.add( new File( p.getBuild().getOutputDirectory() ) ); } File templateCompilationOutputDirectory = getTemplateCompilationOutputDirectory(); AnalysisProcessor sbtAnalysisProcessor = getSbtAnalysisProcessor(); Properties origProperties = System.getProperties(); Properties newProperties = new Properties( project.getProperties() ); newProperties.putAll( origProperties ); if ( serverJvmArgs != null ) { String trimmedServerJvmArgs = serverJvmArgs.trim(); if ( trimmedServerJvmArgs.length() > 0 ) { String[] args = trimmedServerJvmArgs.split( " " ); for ( String arg : args ) { if ( arg.startsWith( "-D" ) ) { getLog().debug( " Setting system property '" + arg + "'" ); String[] prop = arg.substring( 2/*"-D".length()*/ ).split( "=", 2 ); newProperties.setProperty( prop[0], prop[1] ); } } } } newProperties.setProperty( "project.build.directory", project.getBuild().getDirectory() ); Map devSettingsMap = new HashMap(); if ( devSettings != null ) { String trimmedDevSettings = devSettings.trim(); if ( trimmedDevSettings.length() > 0 ) { String[] args = trimmedDevSettings.split( " " ); for ( String arg : args ) { String[] prop = arg.split( "=", 2 ); devSettingsMap.put( prop[0], prop[1] ); } } } String httpPortString = httpPort != null && !"".equals( httpPort ) ? httpPort : System.getProperty( "http.port", devSettingsMap.get( "play.server.http.port" ) ); Integer resolvedHttpPort = parsePortValue( httpPortString, Integer.valueOf( DEFAULT_HTTP_PORT ) ); String httpsPortString = httpsPort != null && !"".equals( httpsPort ) ? httpsPort : System.getProperty( "https.port", devSettingsMap.get( "play.server.https.port" ) ); Integer resolvedHttpsPort = parsePortValue( httpsPortString, null ); if ( resolvedHttpPort == null && resolvedHttpsPort == null ) { throw new MojoExecutionException( "HTTPS port must be specified when HTTP port is disabled" ); } String httpAddressString = httpAddress != null && !"".equals( httpAddress ) ? httpAddress : System.getProperty( "http.address", devSettingsMap.get( "play.server.http.address" ) ); String resolvedHttpAddress = httpAddressString != null ? httpAddressString : DEFAULT_HTTP_ADDRESS; System.setProperties( newProperties ); try { List playDocsClasspath = new ArrayList(); File playDocsFile = null; if ( scalaVersion != null && !"".equals( scalaVersion ) ) { String[] scalaVersionParts = scalaVersion.split( "\\." ); String scalaBinaryVersion = scalaVersionParts[0] + "." + scalaVersionParts[1]; String playDocsArtifactId = play2Runner.getPlayDocsModuleId( scalaBinaryVersion, playVersion ); if ( playDocsArtifactId != null ) { String[] gav = playDocsArtifactId.split( ":" ); Set playDocsArtifacts = getResolvedArtifact( gav[0], gav[1], gav[2] ); getLog().debug( "playDocsClasspath:" ); for ( Artifact dependencyArtifact : playDocsArtifacts ) { File dependencyArtifactFile = dependencyArtifact.getFile(); if ( dependencyArtifact.getGroupId().equals( gav[0] ) && dependencyArtifact.getArtifactId().equals( gav[1] ) ) { playDocsFile = dependencyArtifactFile; } playDocsClasspath.add( dependencyArtifactFile ); getLog().debug( "- " + dependencyArtifactFile.getAbsolutePath() ); } } } FileWatchService playWatchService = null; try { playWatchService = getWatchService(); playWatchService.initialize( new MavenFileWatchLogger( getLog() ) ); } catch ( Exception e ) { getLog().warn( "File watch service initialization failed. Running without hot-reload functionality.", e ); playWatchService = null; } Play2Builder buildLink = new MavenPlay2Builder( allRequiredReactorModules, sourceEncoding, goals, additionalGoals, assetsPrefix, getLog(), session, lifecycleExecutor, container, templateCompilationOutputDirectory, sbtAnalysisProcessor, playWatchService ); Play2RunnerConfiguration configuration = new Play2RunnerConfiguration(); configuration.setBaseDirectory( baseDir ); configuration.setOutputDirectories( outputDirectories ); configuration.setDependencyClasspath( dependencyClasspath ); configuration.setDocsFile( playDocsFile ); configuration.setDocsClasspath( playDocsClasspath ); configuration.setHttpPort( resolvedHttpPort ); configuration.setHttpsPort( resolvedHttpsPort ); configuration.setHttpAddress( resolvedHttpAddress ); configuration.setAssetsPrefix( assetsPrefix ); configuration.setAssetsDirectory( assetsOutputDirectory ); configuration.setDevSettings( devSettingsMap ); configuration.setBuildLink( buildLink ); try { System.out.println(); // for nicer console output Play2DevServer devModeServer = play2Runner.runInDevMode( configuration ); try { getLog().info( "" ); getLog().info( "(Server started, use [Enter] to stop...)" ); getLog().info( "" ); System.in.read(); // buffered read, waits for Enter } finally { devModeServer.close(); } } finally { buildLink.close(); } } catch ( Throwable e ) { throw new MojoExecutionException( "?", e ); } finally { System.setProperties( origProperties ); } } private File getTemplateCompilationOutputDirectory() throws MojoExecutionException { Play2Provider play2Provider = getProvider(); Play2TemplateCompiler compiler = play2Provider.getTemplatesCompiler(); File targetDirectory = new File( project.getBuild().getDirectory() ); String outputDirectoryName = compiler.getCustomOutputDirectoryName(); if ( outputDirectoryName == null ) { outputDirectoryName = AbstractPlay2SourceGeneratorMojo.DEFAULT_TARGET_DIRECTORY_NAME; } File generatedDirectory = new File( targetDirectory, outputDirectoryName + "/main" ); return generatedDirectory; } private Integer parsePortValue( String portValue, Integer defaultValue ) { Integer result = defaultValue; if ( portValue != null ) { if ( "disabled".equals( portValue ) ) { result = null; } else { try { result = Integer.valueOf( portValue ); } catch ( NumberFormatException e ) { throw new RuntimeException( "Invalid port argument: " + portValue, e ); } } } return result; } private FileWatchService getWatchService() throws MojoExecutionException { FileWatchService watchService = null; if ( !watchServices.isEmpty() ) { watchService = getDeclaredWatchService(); } else { watchService = getWellKnownWatchService(); } return watchService; } private FileWatchService getDeclaredWatchService() throws MojoExecutionException { if ( watchServices.size() > 1 ) { throw new MojoExecutionException( "Too many file watch services defined. A maximum of one allowed." ); } Map.Entry watchServiceEntry = watchServices.entrySet().iterator().next(); String watchServiceId = watchServiceEntry.getKey(); FileWatchService watchService = watchServiceEntry.getValue(); getLog().debug( String.format( "Using declared file watch service \"%s\".", watchServiceId ) ); return watchService; } private FileWatchService getWellKnownWatchService() throws MojoExecutionException { try { String watchServiceId = fileWatchService != null ? fileWatchService : FileWatchServices.getDefaultWatchServiceId(); Set watcherArtifacts = getResolvedArtifact( pluginGroupId, "play2-source-watcher-" + watchServiceId, pluginVersion ); List classPathUrls = new ArrayList( watcherArtifacts.size() ); for ( Artifact dependencyArtifact : watcherArtifacts ) { classPathUrls.add( new URL( dependencyArtifact.getFile().toURI().toASCIIString() ) ); } ClassLoader watchServiceClassLoader = new URLClassLoader( classPathUrls.toArray( new URL[classPathUrls.size()] ), Thread.currentThread().getContextClassLoader() ); ServiceLoader watchServiceLoader = ServiceLoader.load( FileWatchService.class, watchServiceClassLoader ); // get first (there should be exactly one) FileWatchService watchService = watchServiceLoader.iterator().next(); getLog().debug( String.format( "Using autodetected file watch service \"%s\".", watchServiceId ) ); return watchService; } catch ( ArtifactResolutionException e ) { throw new MojoExecutionException( "Provider autodetection failed", e ); } catch ( MalformedURLException e ) { throw new MojoExecutionException( "Provider autodetection failed", e ); } } private boolean isReactorProject( List upstreamProjects, Artifact a ) { boolean result = false; for ( MavenProject rp: upstreamProjects ) { if ( rp.getGroupId().equals( a.getGroupId() ) && rp.getArtifactId().equals( a.getArtifactId() ) && rp.getVersion().equals( a.getVersion() ) ) { result = true; break; } } return result; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy