public static final String PROP_ACTION_AUGMENT = PROP_PREFIX + "augment"; public static final String PROP_ACTION_EXECUTABLE = PROP_PREFIX + "executable"; public static final String PROP_ACTION_MODULE = PROP_PREFIX + "module"; public static final String PROP_ACTION_MODULE_OLD = PROP_PREFIX + "metadata"; public static final String PROP_ACTION_RESOLVE = PROP_PREFIX + "resolve"; public static final String PROP_CONFIG_INDEX = CONFIG_PREFIX + "createIndex"; public static final String PROP_PREFIX = "em:"; public static final String PROP_PREFIX_OLD = "_.eccentric.modularity."; public static final String VAL_BND_VERSION = getVersion("bnd"); public static final String VAL_INDEX_TYPE = "index"; @Component(role = BndExportPlugin.class)
public class BndExportPlugin extends DynamicMavenPlugin {

	private Logger logger = LoggerFactory.getLogger(BndExportPlugin.class);

	@Requirement
	private Dependencies dependencies;

	@Requirement
	private Templates templates;

	private Path emProjectHome;
	
	public void addToPomForExport(MavenProject project) throws MavenExecutionException {
		addToPom(project, true);
		try {
			project.getProperties().setProperty(PROP_RESOLVE_OUTPUT, Constants.getExportedModulesFolder(project).toString());
		} catch (IOException e) {
			throw new MavenExecutionException(e.getMessage(), e);
		}
		logger.info("Added `bnd-export-maven-plugin` to genrate list of modules needed at runtume!");
	}

	public void addToPomForExecutable(MavenProject project) throws MavenExecutionException {
		addToPom(project, false);
		logger.info("Added `bnd-export-maven-plugin` to genrate executable jar!");
	}

	public void addToPom(MavenProject project, boolean bundlesOnly) throws MavenExecutionException {

		try {
			emProjectHome = com.commsen.em.maven.util.Constants.getHome(project);
		} catch (IOException e) {
			throw new MavenExecutionException(e.getMessage(), e);
		}
		
		String bndrun = getBndrunPath(project);

		Map<String, Object> model = new HashMap<String, Object>();
		model.put("bundlesOnly", bundlesOnly);
		model.put("bndrun", bndrun);
		model.put("bundles", Collections.EMPTY_LIST);

		String configuration = null;
		try {
			configuration = templates.process("META-INF/templates/bnd-export-maven-plugin-configuration.fmt", model);
		} catch (IOException | TemplateException e) {
			throw new MavenExecutionException("Failed to process template file!", e);
		}

		Plugin plugin = createPlugin("com.commsen.em", "em-maven-plugin", VAL_EXTENSION_VERSION, configuration, "export", "export", "package");
		plugin.setExtensions(true);
		
		project.getBuild().getPlugins().add(0, plugin);

	}

	private String getBndrunPath(MavenProject project) throws MavenExecutionException {
		String bndrunName = project.getProperties().getProperty(Constants.PROP_CONFIG_BNDRUN, "");
		if (bndrunName.trim().isEmpty()) {
			bndrunName = project.getName() + ".bndrun";
		}
		
		File existingBndrunFile = new File(project.getBasedir(), bndrunName);
		File targetBndrunFile = emProjectHome.resolve(bndrunName).toFile();

		if (existingBndrunFile.exists()) {
			if (existingBndrunFile.isFile()) {
				logger.info("Found `" + existingBndrunFile.getName() + "` in project's folder and will use it as is! POM config is ignored!");
				try {
					Files.copy(existingBndrunFile.toPath(), targetBndrunFile.toPath());
				} catch (IOException e) {
					throw new MavenExecutionException("Failed to copy file!", e);
				}
			} else {
				throw new MavenExecutionException("'" + existingBndrunFile + "' is not a file!", project.getFile());
			}
		} else {
			String distro = project.getProperties().getProperty(INTERNAL_DISTRO_FILE, "");
			generateBndrun(project, distro, targetBndrunFile);
		}
		
		return targetBndrunFile.getAbsolutePath();
	}

	private void generateBndrun(MavenProject project, String distro, File bndFile) throws MavenExecutionException {

		Set<String> requirements = new HashSet<>();
		requirements.add("osgi.identity;filter:='(osgi.identity=" + project.getArtifactId() + ")'");

		String contracts = project.getProperties().getProperty(PROP_CONTRACTS);
		if (contracts != null && !contracts.trim().isEmpty()) {
			String[] contractsArray = contracts.split("[\\s]*,[\\s]*");
			for (String contract : contractsArray) {
				requirements.add("em.contract;filter:='(em.contract=" + contract + ")';effective:=assemble");
			}
		}

		Set<String> runProperties = new HashSet<>();
		
		String runPropertiesText = project.getProperties().getProperty(PROP_EXECUTABLE_RUN_PROPERTIES);
		if (runPropertiesText != null && !runPropertiesText.trim().isEmpty()) {
			String[] propertiesArray = runPropertiesText.split("[\\s]*\\n[\\s]*");
			runProperties.addAll(Arrays.asList(propertiesArray));
		}
		
		
		Map<String, Object> model = new HashMap<>();
		model.put("requirements", requirements);
		model.put("runProperties", runProperties);
		model.put("distro", distro);

		String bndrunContent = null;
		try {
			bndrunContent = templates.process("META-INF/templates/bndrun.fmt", model);
		} catch (IOException | TemplateException e) {
			throw new MavenExecutionException("Failed to process template file!", e);
		}

		if (logger.isDebugEnabled())
			logger.debug("Generated bndrun file: \n{}", bndrunContent);

		writeBndrun(bndFile, bndrunContent);

	}

	private void writeBndrun(File generatedBndrunFile, String content/*, boolean deleteOnExit*/) throws MavenExecutionException {

		try {
			generatedBndrunFile.createNewFile();
			PrintWriter writer = new PrintWriter(generatedBndrunFile, "UTF-8");
			writer.print(content.replaceAll("\\t", "    "));
			writer.close();
		} catch (IOException e) {
			throw new MavenExecutionException("Failed to create temprary bndrun file!", e);
		}
	}

} @Component(role = BndIndexerPlugin.class)
public class BndIndexerPlugin extends DynamicMavenPlugin {

	private Logger logger = LoggerFactory.getLogger(BndIndexerPlugin.class);


	public void addToPom(MavenProject project) throws MavenExecutionException {

		String configuration = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" //
				+ "<configuration>" //
				+ "		<localURLs>ALLOWED</localURLs>" //
				+ "		<includeJar>true</includeJar>" //
				+ "		<outputFile>${project.build.directory}/index/index.xml</outputFile>" //
				+ "</configuration>"; //

		Plugin plugin = createPlugin("biz.aQute.bnd", "bnd-indexer-maven-plugin", VAL_BND_VERSION, configuration,
				"index", "index", null);

		project.getBuild().getPlugins().add(0, plugin);

	}

	public void addToPomForIndexingTmpBundles(MavenProject project) throws MavenExecutionException {

		Path genertedModules;
		try {
			genertedModules = Constants.getGeneratedModulesFolder(project);
		} catch (IOException e) {
			throw new MavenExecutionException(e.getMessage(), e);
		} 
		
		String configuration = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" //
				+ "<configuration>" //
				+ "		<inputDir>" //
				+ 			genertedModules //
				+ "		</inputDir>" //
				+ "		<outputFile>${project.build.directory}/index/index.xml</outputFile>" //
				+ "</configuration>"; //

		Plugin plugin = createPlugin("biz.aQute.bnd", "bnd-indexer-maven-plugin", VAL_BND_VERSION, configuration,
				"index", "local-index", null);

		project.getBuild().getPlugins().add(0, plugin);

		logger.info("Added `bnd-indexer-maven-plugin` to genrate an index of detected modules!");

	}

	public void configureForIndexGeneration(MavenProject project) throws MavenExecutionException {

		Plugin plugin = getPlugin(project, "biz.aQute.bnd:bnd-indexer-maven-plugin");

		if (plugin != null) {

			String configuration = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" //
					+ "<configuration>" //
					+ "		<localURLs>ALLOWED</localURLs>" //
					+ "		<includeJar>false</includeJar>" //
					+ "		<outputFile>${project.build.directory}/index.xml</outputFile>" //
					+ "</configuration>"; //
			
			configurePlugin(plugin, "default-index", configuration);

			logger.info("Configuring `bnd-indexer-maven-plugin` to genrate an index from project's dependencies!");

		}
	}
} @Component(role = BndPlugin.class)
public class BndPlugin extends DynamicMavenPlugin {

	private Logger logger = LoggerFactory.getLogger(BndPlugin.class);

	@Requirement
	private Templates templates;
	
	public void addToBuild(MavenProject project) throws MavenExecutionException {

		String includePackages = project.getProperties().getProperty(PROP_MODULE_INCLUDE_PACKAGES, "");
		String importPackages = project.getProperties().getProperty(PROP_MODULE_IMPORT_PACKAGES, "");
		String ignorePackages = project.getProperties().getProperty(PROP_MODULE_IGNORE_PACKAGES, "");
		
		StringBuilder importStatement = new StringBuilder();
		if (!ignorePackages.isEmpty()) {
			Arrays.stream(ignorePackages.split(",")).forEach(p -> importStatement.append("!").append(p).append(","));
		}
		if (!importPackages.isEmpty()) {
			importStatement.append(importPackages).append(",");
		}
		importStatement.append("*");
		
		Map<String, Object> model = new HashMap<String, Object>();
		model.put("includePackages", includePackages);
		model.put("importStatement", importStatement);
		
		String bndContent = null;
		try {
			bndContent = templates.process("META-INF/templates/bnd.fmt", model);
		} catch (IOException | TemplateException e) {
			throw new MavenExecutionException("Failed to process template file!", e);
		}
				
		StringBuilder configuration = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") //
				.append("<configuration><bnd><![CDATA[ \n") //
				.append(bndContent)
				.append("]]></bnd></configuration>"); //

		logger.debug("Generated bnd-maven-plugin confgiguration: \n {}", configuration);
		
		project.getBuild().getPlugins().add(0, preparePlugin(configuration.toString()));

		configureCompilerPlugin(project);
		configureJarPlugin(project);
		
		logger.info("Added `bnd-maven-plugin` to generate metadata");

	}


	public void addToBuildForAugment(MavenProject project) throws MavenExecutionException {

		String path = project.getProperties().getProperty(PROP_AUGMENT_FILE, "META-INF/augments.bnd");
		
		StringBuilder configuration = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") //
				.append("<configuration><bnd><![CDATA[ \n") //
				.append("	Provide-Capability: bnd.augment; path='").append(path).append("'\n") //
				.append("]]></bnd></configuration>"); //

		logger.debug("Generated bnd-maven-plugin confgiguration: \n {}", configuration);

		project.getBuild().getPlugins().add(0, preparePlugin(configuration.toString()));

		configureJarPlugin(project);
		
		logger.info("Added 'bnd-maven-plugin' to genrate jar that augments (provides metadata for) other jars!");

	}


	private Plugin preparePlugin(String configuration) throws MavenExecutionException {
		Plugin plugin = createPlugin( //
				"biz.aQute.bnd", //
				"bnd-maven-plugin", //
				VAL_BND_VERSION, //
				configuration, //
				"bnd-process", //
				"bnd-process", null);
		return plugin;
	}
	
	
	private void configureCompilerPlugin(MavenProject project) throws MavenExecutionException {
		Plugin compilerPlugin = getPlugin(project, "org.apache.maven.plugins:maven-compiler-plugin");

		if (compilerPlugin != null) {
			compilerPlugin.setVersion("3.7.0");
			StringBuilder compilerConfig = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") //
					.append("<configuration>") //
					.append("	<annotationProcessorPaths>") //
					.append("		<path>") //
					.append("			<groupId>com.commsen.em</groupId>") //
					.append("			<artifactId>em.annotation.processors</artifactId>") //
					.append("			<version>").append(Constants.VAL_EXTENSION_VERSION).append("</version>") //
					.append("		</path>") //
					.append("	</annotationProcessorPaths>") //
					.append("</configuration>");

			configurePlugin(compilerPlugin, "default-compile", compilerConfig.toString());
		}
	}
	
	private void configureJarPlugin(MavenProject project) throws MavenExecutionException {
		Plugin jarPlugin = getPlugin(project, "org.apache.maven.plugins:maven-jar-plugin");

		if (jarPlugin != null) {
			StringBuilder jarConfig = new StringBuilder("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n") //
					.append("<configuration>") //
					.append("	<archive>\n") //
					.append("		<manifestFile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestFile>\n") //
					.append("	</archive>") //
					.append("</configuration>");

			configurePlugin(jarPlugin, "default-jar", jarConfig.toString());
		}
	}

} @Component(role = DistroPlugin.class)
public class DistroPlugin {

	private Logger logger = LoggerFactory.getLogger(DistroPlugin.class);


	public void createDistroJar(MavenProject project, String remote) throws MavenExecutionException {

		Path emProjectDistro;
		try {
			emProjectDistro = Constants.getDistroFolder(project);
		} catch (IOException e) {
			throw new MavenExecutionException("Failed to create distro folder!", e);
		}

		bnd bnd = new bnd();
		try {
			String[] params = remote.split(":");
			if (params.length != 4) {
				throw new MavenExecutionException("Invalid remote. The format is `host:port:name:version` ",
						project.getFile());
			}

			File distroFile = emProjectDistro.resolve("-" + params[3] + ".jar").toFile();

			List<String> args = new LinkedList<String>();
			args.add("remote");
			args.add("-h");
			args.add(params[0]);
			args.add("-p");
			args.add(params[1]);
			args.add("distro");
			args.add("-o");
			args.add(distroFile.getAbsolutePath());
			args.add(params[2]);
			args.add(params[3]);

			logger.info("  - Building a distro jar (matadata only) for provided target runtime " + remote);
			logger.debug("    Running bnd with the following args : " + args);
			try {
				/*
				 *  TODO Figure out how to provide a connection timeout (hangs forever with wrong host/port) 
				 *  Perhaps run in separate thread
				 */
				bnd.start(args.toArray(new String[args.size()]));
			} catch (Exception e) {
				throw new MavenExecutionException("Failed to build distro jar", e);
			}
			logger.info("  - Saved distro jar in " + distroFile);

			project.getProperties().setProperty(INTERNAL_DISTRO_FILE, distroFile.getAbsolutePath());

		} finally {
			try {
				bnd.close();
			} catch (IOException e) {
				logger.error("Failed to properly close bnd!", e);
			}
		}
	}


} @Component(role = EmRegisterContractPlugin.class)
public class EmRegisterContractPlugin extends DynamicMavenPlugin {

	private Logger logger = LoggerFactory.getLogger(EmRegisterContractPlugin.class);


	public void addToPom(MavenProject project) throws MavenExecutionException {
		Plugin plugin = createPlugin("com.commsen.em", "em-maven-plugin", VAL_EXTENSION_VERSION, null, "registerContract", "registerContract", "package");
		project.getBuild().getPlugins().add(0, plugin);
		logger.info("Added `em-maven-plugin` to register contracts!");

	}


} @Component(role = LinkPlugin.class)
public class LinkPlugin extends DynamicMavenPlugin {

	private Logger logger = LoggerFactory.getLogger(LinkPlugin.class);

	@Requirement
	private Templates templates;
	
	public void addToBuild(MavenProject project) throws MavenExecutionException {

		project.getBuild().getPlugins().add(0, preparePlugin());
		logger.info("Added `em-maven-plugin:link` to keep track of modules");
	}

	private Plugin preparePlugin() throws MavenExecutionException {
		Plugin plugin = createPlugin( //
				"com.commsen.em", //
				"em-maven-plugin", //
				VAL_EXTENSION_VERSION, //
				null, //
				"link", //
				"link", //
				"package");
		return plugin;
	}
	

}
import static com.commsen.em.maven.util.Constants.PROP_ACTION_AUGMENT;
import static com.commsen.em.maven.util.Constants.PROP_ACTION_EXECUTABLE;
import static com.commsen.em.maven.util.Constants.PROP_ACTION_MODULE;
import static com.commsen.em.maven.util.Constants.PROP_ACTION_MODULE_OLD;
import static com.commsen.em.maven.util.Constants.PROP_ACTION_RESOLVE;
import static com.commsen.em.maven.util.Constants.PROP_CONFIG_INDEX;
import static com.commsen.em.maven.util.Constants.PROP_PREFIX;
import static com.commsen.em.maven.util.Constants.PROP_PREFIX_OLD;
import static com.commsen.em.maven.util.Constants.VAL_BND_VERSION;
import static com.commsen.em.maven.util.Constants.VAL_INDEX_TYPE;
import java.util.LinkedList;
import java.util.List;
import org.apache.maven.MavenExecutionException;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.MavenArtifactRepository;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.execution.AbstractExecutionListener;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.ExecutionListener;
import org.apache.maven.project.MavenProject;
import org.apache.maven.project.ProjectBuildingRequest;
import org.codehaus.plexus.component.annotations.Component;
import org.codehaus.plexus.component.annotations.Requirement;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.commsen.em.maven.plugins.BndExportPlugin;
import com.commsen.em.maven.plugins.BndIndexerPlugin;
import com.commsen.em.maven.plugins.BndPlugin;
import com.commsen.em.maven.plugins.DistroPlugin;
import com.commsen.em.maven.plugins.EmRegisterContractPlugin;
import com.commsen.em.maven.plugins.LinkPlugin;
package com.commsen.em.maven.extension;





@Component(role = ExecutionListener.class, hint = "em")
public class EccentricModularityExecutionListener extends AbstractExecutionListener {

	@Requirement
	private BndPlugin bndPlugin;

	@Requirement
	private BndIndexerPlugin bndIndexerPlugin;

	@Requirement
	private BndExportPlugin bndExportPlugin;

	@Requirement
	private DistroPlugin distroPlugin;

	@Requirement
	private EmRegisterContractPlugin contractExporterPlugin;

	@Requirement
	private LinkPlugin linkPlugin;

	@Requirement(role = ArtifactRepositoryLayout.class, hint = "default")
	private ArtifactRepositoryLayout defaultLayout;

	private ExecutionListener delegate;

	private Logger logger = LoggerFactory.getLogger(EccentricModularityExecutionListener.class);

	public void setDelegate(ExecutionListener executionListener) {
		this.delegate = executionListener;
	}

	@Override
	public void projectStarted(ExecutionEvent event) {
		delegate.projectStarted(event);
		projectStarted(event.getSession().getProjectBuildingRequest(), event.getProject());
	}

	public void projectStarted(ProjectBuildingRequest projectBuildingRequest, MavenProject project) {
		
		boolean hasOld = project.getProperties().keySet().stream() //
				.filter(p -> p.toString().startsWith(PROP_PREFIX_OLD)) //
				.peek(p -> logger.error("Property '{}' uses old prefix! Use '{}' instead!", p,
						p.toString().replace(PROP_PREFIX_OLD, PROP_PREFIX))) //
				.count() > 0;

		if (hasOld) {
			throw new RuntimeException("Can't build project using old properties!");
		}

		hasOld = project.getProperties().keySet().stream() //
				.filter(p -> p.toString().startsWith(PROP_ACTION_MODULE_OLD))
				.peek(p -> logger.error("Property '{}' uses old syntax! Use '{}' instead!", p,
						p.toString().replace(PROP_ACTION_MODULE_OLD, PROP_ACTION_MODULE)))