PageRenderTime 31ms CodeModel.GetById 0ms RepoModel.GetById 0ms app.codeStats 0ms

/amps-maven-plugin/src/main/java/com/atlassian/maven/plugins/amps/product/FeCruProductHandler.java

https://bitbucket.org/atlassian/amps
Java | 385 lines | 294 code | 58 blank | 33 comment | 17 complexity | 8f2c44cd62f43a97d5ab1da80826c2be MD5 | raw file
Possible License(s): Apache-2.0, BSD-3-Clause
  1. package com.atlassian.maven.plugins.amps.product;
  2. import com.atlassian.maven.plugins.amps.MavenContext;
  3. import com.atlassian.maven.plugins.amps.MavenGoals;
  4. import com.atlassian.maven.plugins.amps.Node;
  5. import com.atlassian.maven.plugins.amps.Product;
  6. import com.atlassian.maven.plugins.amps.ProductArtifact;
  7. import com.atlassian.maven.plugins.amps.util.ConfigFileUtils.Replacement;
  8. import com.atlassian.maven.plugins.amps.util.ZipUtils;
  9. import com.atlassian.maven.plugins.amps.util.ant.AntJavaExecutorThread;
  10. import com.atlassian.maven.plugins.amps.util.ant.JavaTaskFactory;
  11. import org.apache.commons.io.FileUtils;
  12. import org.apache.maven.artifact.Artifact;
  13. import org.apache.maven.artifact.resolver.ArtifactResolver;
  14. import org.apache.maven.artifact.versioning.ArtifactVersion;
  15. import org.apache.maven.plugin.MojoExecutionException;
  16. import org.apache.maven.repository.RepositorySystem;
  17. import org.apache.tools.ant.taskdefs.Java;
  18. import org.apache.tools.ant.types.Path;
  19. import javax.annotation.Nonnull;
  20. import java.io.File;
  21. import java.io.IOException;
  22. import java.net.Socket;
  23. import java.text.DateFormat;
  24. import java.text.ParsePosition;
  25. import java.text.SimpleDateFormat;
  26. import java.util.Arrays;
  27. import java.util.Collection;
  28. import java.util.Date;
  29. import java.util.List;
  30. import java.util.Map;
  31. import java.util.Optional;
  32. import static com.atlassian.maven.plugins.amps.util.ProjectUtils.createDirectory;
  33. import static com.atlassian.maven.plugins.amps.util.ZipUtils.unzip;
  34. import static com.atlassian.maven.plugins.amps.util.ant.JavaTaskFactory.output;
  35. import static java.lang.Thread.currentThread;
  36. import static java.util.Collections.singletonMap;
  37. import static org.apache.commons.io.FileUtils.moveToDirectory;
  38. /**
  39. * The {@link ProductHandler} for Fisheye / Crucible.
  40. */
  41. public class FeCruProductHandler extends AbstractProductHandler {
  42. private static final int STARTUP_CHECK_DELAY = 1000;
  43. private static final String FISHEYE_INST = "fisheye.inst";
  44. private static final String PRODUCT_GROUP_ID = "com.atlassian.crucible";
  45. private static final String PRODUCT_ARTIFACT_ID = "atlassian-crucible";
  46. private static final String TEST_DATA_GROUP_ID = "com.atlassian.fecru";
  47. private static final String TEST_DATA_ARTIFACT_ID = "amps-fecru";
  48. private static final String FECRU_TIMESTAMP_FORMAT = "yyyyMMddHHmmss";
  49. /**
  50. * Indicates whether the given version of the given artifact is an official Fecru release.
  51. *
  52. * @param version the version of the artifact
  53. * @param artifact the artifact
  54. * @return see description
  55. * @since 8.1.2
  56. */
  57. public static boolean isFecruRelease(final ArtifactVersion version, final Artifact artifact) {
  58. return isFecruArtifact(artifact) && isFecruTimestamp(version.getQualifier());
  59. }
  60. private static boolean isFecruTimestamp(final String qualifier) {
  61. final DateFormat dateFormat = new SimpleDateFormat(FECRU_TIMESTAMP_FORMAT);
  62. dateFormat.setLenient(false);
  63. final Date releaseDate = dateFormat.parse(qualifier, new ParsePosition(0));
  64. return releaseDate != null;
  65. }
  66. public static boolean isFecruArtifact(final Artifact artifact) {
  67. return isProductArtifact(artifact) || isTestResourcesArtifact(artifact);
  68. }
  69. public static boolean isProductArtifact(final Artifact artifact) {
  70. return PRODUCT_GROUP_ID.equals(artifact.getGroupId()) && PRODUCT_ARTIFACT_ID.equals(artifact.getArtifactId());
  71. }
  72. public static boolean isTestResourcesArtifact(final Artifact artifact) {
  73. return TEST_DATA_GROUP_ID.equals(artifact.getGroupId()) && TEST_DATA_ARTIFACT_ID.equals(artifact.getArtifactId());
  74. }
  75. private final JavaTaskFactory javaTaskFactory;
  76. public FeCruProductHandler(final MavenContext context, final MavenGoals goals,
  77. final RepositorySystem repositorySystem, final ArtifactResolver artifactResolver) {
  78. super(context, goals, new FeCruPluginProvider(), repositorySystem, artifactResolver);
  79. this.javaTaskFactory = new JavaTaskFactory(log);
  80. }
  81. @Nonnull
  82. @Override
  83. public String getId() {
  84. return ProductHandlerFactory.FECRU;
  85. }
  86. @Nonnull
  87. public ProductArtifact getArtifact() {
  88. return new ProductArtifact(PRODUCT_GROUP_ID, PRODUCT_ARTIFACT_ID, "RELEASE");
  89. }
  90. @Override
  91. public int getDefaultHttpPort() {
  92. return 3990;
  93. }
  94. @Override
  95. public int getDefaultHttpsPort() {
  96. return 8443;
  97. }
  98. public final void stop(@Nonnull final Product product) throws MojoExecutionException {
  99. final int webPort = product.getSingleNodeWebPort();
  100. log.info("Stopping " + product.getInstanceId() + " on ports "
  101. + webPort + " (http) and " + controlPort(webPort) + " (control)");
  102. try {
  103. execFishEyeCmd("stop", product, false);
  104. } catch (Exception e) {
  105. throw new MojoExecutionException(
  106. "Failed to stop FishEye/Crucible instance at " + product.getServer() + ":" + webPort);
  107. }
  108. waitForFishEyeToStop(product);
  109. }
  110. @Nonnull
  111. @Override
  112. protected List<Replacement> getReplacements(@Nonnull final Product product, final int nodeIndex) {
  113. final List<Replacement> replacements = super.getReplacements(product, nodeIndex);
  114. final File homeDirectory = getHomeDirectory(product);
  115. final int webPort = product.getSingleNodeWebPort();
  116. replacements.add(new Replacement("@CONTROL_BIND@", String.valueOf(controlPort(webPort))));
  117. replacements.add(new Replacement("@HTTP_BIND@", String.valueOf(webPort)));
  118. replacements.add(new Replacement("@HTTP_CONTEXT@", String.valueOf(product.getContextPath()), false));
  119. replacements.add(new Replacement("@HOME_DIR@", homeDirectory.getAbsolutePath()));
  120. replacements.add(new Replacement("@SITE_URL@", siteUrl(product)));
  121. return replacements;
  122. }
  123. @Nonnull
  124. @Override
  125. protected List<File> getConfigFiles(@Nonnull final Product product, @Nonnull final File homeDir) {
  126. final List<File> configFiles = super.getConfigFiles(product, homeDir);
  127. configFiles.add(new File(homeDir, "config.xml"));
  128. configFiles.add(new File(homeDir, "var/data/crudb/crucible.script"));
  129. return configFiles;
  130. }
  131. @Override
  132. @Nonnull
  133. protected File extractApplication(final Product product) throws MojoExecutionException {
  134. final File appDir = createDirectory(getAppDirectory(product));
  135. final ProductArtifact artifact = getArtifact(product);
  136. final File cruDistZip = goals.copyZip(getBuildDirectory(), artifact, "test-dist.zip");
  137. try {
  138. // We remove one level of root folder from the zip if present
  139. final int nestingLevel = ZipUtils.countNestingLevel(cruDistZip);
  140. unzip(cruDistZip, appDir.getPath(), nestingLevel > 0 ? 1 : 0);
  141. } catch (final IOException ex) {
  142. throw new MojoExecutionException("Unable to extract application ZIP artifact", ex);
  143. }
  144. return appDir;
  145. }
  146. protected File getAppDirectory(Product ctx) {
  147. return new File(getBaseDirectory(ctx), ctx.getId() + "-" + ctx.getVersion());
  148. }
  149. @Nonnull
  150. @Override
  151. public final Optional<ProductArtifact> getTestResourcesArtifact() {
  152. return Optional.of(new ProductArtifact(TEST_DATA_GROUP_ID, TEST_DATA_ARTIFACT_ID));
  153. }
  154. @Nonnull
  155. @Override
  156. protected Map<String, String> getSystemProperties(final Product product, final int nodeIndex) {
  157. return singletonMap(FISHEYE_INST, getHomeDirectory(product).getAbsolutePath());
  158. }
  159. private File getHomeDirectory(final Product product) {
  160. final List<File> homeDirectories = getHomeDirectories(product);
  161. if (homeDirectories.size() != 1) {
  162. throw new IllegalStateException("Expected one home directory but found " + homeDirectories);
  163. }
  164. return homeDirectories.get(0);
  165. }
  166. @Override
  167. @Nonnull
  168. protected File getBundledPluginPath(Product product, File productDir) {
  169. return new File(productDir, "plugins/bundled-plugins.zip");
  170. }
  171. @Nonnull
  172. @Override
  173. protected final Optional<File> getUserInstalledPluginsDirectory(final Product product, File appDir, File homeDir) {
  174. return Optional.of(new File(new File(new File(homeDir, "var"), "plugins"), "user"));
  175. }
  176. @Override
  177. protected boolean supportsStaticPlugins() {
  178. return false;
  179. }
  180. @Override
  181. @Nonnull
  182. protected final List<Node> startProduct(
  183. final Product product, final File productFile, final List<Map<String, String>> systemProperties)
  184. throws MojoExecutionException {
  185. final int webPort = product.getSingleNodeWebPort();
  186. log.info("Starting " + product.getInstanceId() + " on ports "
  187. + webPort + " (http) and " + controlPort(webPort) + " (control)");
  188. AntJavaExecutorThread thread;
  189. try {
  190. thread = execFishEyeCmd("run", product, true);
  191. } catch (Exception e) {
  192. throw new MojoExecutionException("Error starting fisheye.", e);
  193. }
  194. waitForFishEyeToStart(product, thread);
  195. return product.getNodes();
  196. }
  197. private void waitForFishEyeToStart(final Product ctx, final AntJavaExecutorThread thread)
  198. throws MojoExecutionException {
  199. boolean connected = false;
  200. int waited = 0;
  201. final int webPort = ctx.getSingleNodeWebPort();
  202. while (!connected) {
  203. try {
  204. Thread.sleep(STARTUP_CHECK_DELAY);
  205. } catch (final InterruptedException e) {
  206. // ignore
  207. currentThread().interrupt();
  208. }
  209. try {
  210. new Socket("localhost", webPort).close();
  211. connected = true;
  212. } catch (IOException e) {
  213. // ignore
  214. }
  215. if (thread.isFinished()) {
  216. throw new MojoExecutionException("Fisheye failed to start.", thread.getBuildException());
  217. }
  218. if (waited++ * STARTUP_CHECK_DELAY > ctx.getStartupTimeout()) {
  219. throw new MojoExecutionException("FishEye took longer than " + ctx.getStartupTimeout() + "ms to start!");
  220. }
  221. }
  222. }
  223. private void waitForFishEyeToStop(final Product ctx) throws MojoExecutionException {
  224. boolean connected = true;
  225. int waited = 0;
  226. final int webPort = ctx.getSingleNodeWebPort();
  227. while (connected) {
  228. try {
  229. Thread.sleep(STARTUP_CHECK_DELAY);
  230. } catch (InterruptedException e) {
  231. // ignore
  232. currentThread().interrupt();
  233. }
  234. try {
  235. new Socket("localhost", webPort).close();
  236. } catch (IOException e) {
  237. connected = false;
  238. }
  239. if (waited++ * STARTUP_CHECK_DELAY > ctx.getShutdownTimeout()) {
  240. throw new MojoExecutionException("FishEye took longer than " + ctx.getShutdownTimeout() + "ms to stop!");
  241. }
  242. }
  243. }
  244. private AntJavaExecutorThread execFishEyeCmd(
  245. final String bootCommand, final Product product, final boolean useDebugArgs) {
  246. final List<Map<String, String>> systemProperties = mergeSystemProperties(product);
  247. if (systemProperties.size() != 1) {
  248. // Fecru only supports single-node operation
  249. throw new IllegalStateException("Expected one map but found " + systemProperties);
  250. }
  251. final Java java = javaTaskFactory.newJavaTask(
  252. output(product.getOutput()).
  253. systemProperties(systemProperties.get(0)).
  254. jvmArgs(product.getJvmArgs() + (useDebugArgs ? product.getSingleNodeDebugArgs() : "")));
  255. final Path classpath = java.createClasspath();
  256. classpath.createPathElement().setLocation(new File(getAppDirectory(product), "fisheyeboot.jar"));
  257. java.setClassname("com.cenqua.fisheye.FishEyeCtl");
  258. java.createArg().setValue(bootCommand);
  259. final AntJavaExecutorThread javaThread = new AntJavaExecutorThread(java);
  260. javaThread.start();
  261. return javaThread;
  262. }
  263. protected File getBuildDirectory() {
  264. return new File(project.getBuild().getDirectory());
  265. }
  266. private String siteUrl(final Product ctx) {
  267. return "http://" + ctx.getServer() + ":" + ctx.getSingleNodeWebPort() + ctx.getContextPath();
  268. }
  269. /**
  270. * The control port is the HTTP port with a "1" appended to it, e.g. 3990 becomes 39901.
  271. *
  272. * @param httpPort the HTTP port
  273. * @return the control port
  274. */
  275. private static int controlPort(final int httpPort) {
  276. return httpPort * 10 + 1;
  277. }
  278. private static class FeCruPluginProvider extends AbstractPluginProvider {
  279. @Override
  280. protected Collection<ProductArtifact> getSalArtifacts(String salVersion) {
  281. return Arrays.asList(
  282. new ProductArtifact("com.atlassian.sal", "sal-api", salVersion),
  283. new ProductArtifact("com.atlassian.sal", "sal-fisheye-plugin", salVersion)
  284. );
  285. }
  286. }
  287. @Override
  288. protected void cleanupProductHomeForZip(@Nonnull final Product product, @Nonnull final File homeDirectory)
  289. throws MojoExecutionException, IOException {
  290. super.cleanupProductHomeForZip(product, homeDirectory);
  291. FileUtils.deleteQuietly(new File(homeDirectory, "var/log"));
  292. FileUtils.deleteQuietly(new File(homeDirectory, "var/plugins"));
  293. FileUtils.deleteQuietly(new File(homeDirectory, "cache/plugins"));
  294. }
  295. @Override
  296. @Nonnull
  297. protected String getLibArtifactTargetDir() {
  298. return "lib";
  299. }
  300. /**
  301. * Overridden as most Fecru ZIPped test data is of a different non-standard structure,
  302. * i.e. standard structure should have a single root folder.
  303. * If it finds something looking like the old structure, it will re-organize the data to match what is expected
  304. * of the new structure.
  305. *
  306. * @param tmpDir the temp directory
  307. * @param product the product
  308. * @return the root dir
  309. * @throws IOException if an I/O error occurs
  310. */
  311. @Nonnull
  312. @Override
  313. protected File getRootDir(final File tmpDir, final Product product) throws IOException {
  314. final File[] topLevelFiles = Optional.ofNullable(tmpDir.listFiles()).orElse(new File[0]);
  315. if (topLevelFiles.length == 1) {
  316. return topLevelFiles[0];
  317. }
  318. log.info("Non-standard zip structure identified. Assume using older non-standard FECRU zip format");
  319. log.info("Therefore reorganise unpacked data directories to match standard format.");
  320. final File tmpGen = new File(tmpDir, "generated-resources");
  321. final File tmpHome = new File(tmpGen, product.getId() + "-home");
  322. for (final File file : topLevelFiles) {
  323. moveToDirectory(file, tmpHome, true);
  324. }
  325. return tmpGen;
  326. }
  327. }