1   package com.atlassian.maven.plugins.amps.product;
2   
3   import static com.atlassian.maven.plugins.amps.util.ZipUtils.unzip;
4   import static com.atlassian.maven.plugins.amps.util.ant.JavaTaskFactory.output;
5   
6   import java.io.File;
7   import java.io.IOException;
8   import java.net.Socket;
9   import java.util.Arrays;
10  import java.util.Collection;
11  import java.util.Collections;
12  import java.util.HashMap;
13  import java.util.List;
14  import java.util.Map;
15  
16  import com.atlassian.maven.plugins.amps.MavenContext;
17  
18  import com.atlassian.maven.plugins.amps.util.JvmArgsFix;
19  import org.apache.commons.io.FileUtils;
20  import org.apache.maven.plugin.MojoExecutionException;
21  import org.apache.tools.ant.taskdefs.Java;
22  import org.apache.tools.ant.types.Path;
23  
24  import com.atlassian.maven.plugins.amps.MavenGoals;
25  import com.atlassian.maven.plugins.amps.Product;
26  import com.atlassian.maven.plugins.amps.ProductArtifact;
27  import com.atlassian.maven.plugins.amps.util.ConfigFileUtils.Replacement;
28  import com.atlassian.maven.plugins.amps.util.ZipUtils;
29  import com.atlassian.maven.plugins.amps.util.ant.AntJavaExecutorThread;
30  import com.atlassian.maven.plugins.amps.util.ant.JavaTaskFactory;
31  
32  import static com.atlassian.maven.plugins.amps.util.ProjectUtils.createDirectory;
33  import static com.atlassian.maven.plugins.amps.util.ProjectUtils.firstNotNull;
34  
35  public class FeCruProductHandler extends AbstractProductHandler
36  {
37      private static final int STARTUP_CHECK_DELAY = 1000;
38      private static final String FISHEYE_INST = "fisheye.inst";
39  
40      private final JavaTaskFactory javaTaskFactory;
41  
42      public FeCruProductHandler(MavenContext context, MavenGoals goals)
43      {
44          super(context, goals, new FeCruPluginProvider());
45          this.javaTaskFactory = new JavaTaskFactory(log);
46      }
47  
48      public String getId()
49      {
50          return ProductHandlerFactory.FECRU;
51      }
52  
53      protected ProductArtifact getArtifact()
54      {
55          return new ProductArtifact("com.atlassian.crucible", "atlassian-crucible", "RELEASE");
56      }
57  
58      public int getDefaultHttpPort()
59      {
60          return 3990;
61      }
62  
63      public final void stop(Product ctx) throws MojoExecutionException
64      {
65          log.info("Stopping " + ctx.getInstanceId() + " on ports "
66                  + ctx.getHttpPort() + " (http) and " + controlPort(ctx.getHttpPort()) + " (control)");
67          try
68          {
69              execFishEyeCmd("stop", ctx);
70          }
71          catch (Exception e)
72          {
73              throw new MojoExecutionException("Failed to stop FishEye/Crucible instance at " + ctx.getServer() + ":" + ctx.getHttpPort());
74          }
75  
76          waitForFishEyeToStop(ctx);
77      }
78  
79      @Override
80      protected void extractProductHomeData(File productHomeZip, File homeDir, Product ctx)
81              throws MojoExecutionException
82      {
83          try
84          {
85              unzip(productHomeZip, homeDir.getPath());
86          }
87          catch (final IOException ex)
88          {
89              throw new MojoExecutionException("Unable to copy home directory", ex);
90          }
91      }
92  
93      @Override
94      public List<Replacement> getReplacements(Product ctx)
95      {
96          List<Replacement> replacements = super.getReplacements(ctx);
97          File homeDirectory = getHomeDirectory(ctx);
98          replacements.add(new Replacement("@CONTROL_BIND@", String.valueOf(controlPort(ctx.getHttpPort()))));
99          replacements.add(new Replacement("@HTTP_BIND@", String.valueOf(ctx.getHttpPort())));
100         replacements.add(new Replacement("@HTTP_CONTEXT@", String.valueOf(ctx.getContextPath()), false));
101         replacements.add(new Replacement("@HOME_DIR@", String.valueOf(homeDirectory.getAbsolutePath())));
102         replacements.add(new Replacement("@SITE_URL@", String.valueOf(siteUrl(ctx))));
103         return replacements;
104     }
105 
106     @Override
107     public List<File> getConfigFiles(Product product, File homeDir)
108     {
109         List<File> configFiles = super.getConfigFiles(product, homeDir);
110         configFiles.add(new File(homeDir, "config.xml"));
111         configFiles.add(new File(homeDir, "var/data/crudb/crucible.script"));
112         return configFiles;
113     }
114 
115     @Override
116     protected File extractApplication(Product ctx, File homeDir) throws MojoExecutionException
117     {
118         File appDir = createDirectory(getAppDirectory(ctx));
119 
120         ProductArtifact defaults = getArtifact();
121         ProductArtifact artifact = new ProductArtifact(
122                 firstNotNull(ctx.getGroupId(), defaults.getGroupId()),
123                 firstNotNull(ctx.getArtifactId(), defaults.getArtifactId()),
124                 firstNotNull(ctx.getVersion(), defaults.getVersion()));
125 
126         final File cruDistZip = goals.copyDist(getBuildDirectory(), artifact);
127 
128         try
129         {
130             // We remove one level of root folder from the zip if present
131             int nestingLevel = ZipUtils.countNestingLevel(cruDistZip);
132             unzip(cruDistZip, appDir.getPath(), nestingLevel > 0 ? 1 : 0);
133         }
134         catch (final IOException ex)
135         {
136             throw new MojoExecutionException("Unable to extract application ZIP artifact", ex);
137         }
138 
139         return appDir;
140     }
141 
142     protected File getAppDirectory(Product ctx)
143     {
144         return new File(getBaseDirectory(ctx), ctx.getId() + "-" + ctx.getVersion());
145     }
146 
147     @Override
148     protected final ProductArtifact getTestResourcesArtifact()
149     {
150           return new ProductArtifact("com.atlassian.fecru", "amps-fecru");
151     }
152 
153     @Override
154     protected Map<String, String> getSystemProperties(final Product ctx)
155     {
156         return new HashMap<String, String>()
157         {{
158             put(FISHEYE_INST, getHomeDirectory(ctx).getAbsolutePath());
159         }};
160     }
161 
162     @Override
163     protected String getBundledPluginPath(Product ctx)
164     {
165         return "plugins/bundled-plugins.zip";
166     }
167 
168     @Override
169     protected Collection<? extends ProductArtifact> getDefaultBundledPlugins()
170     {
171         return Collections.emptySet();
172     }
173 
174     @Override
175     protected Collection<? extends ProductArtifact> getDefaultLibPlugins()
176     {
177         return Collections.emptySet();
178     }
179 
180     @Override
181     protected final File getUserInstalledPluginsDirectory(File appDir, File homeDir)
182     {
183         return new File(new File(new File(homeDir, "var"), "plugins"), "user");
184     }
185 
186     @Override
187     protected boolean supportsStaticPlugins()
188     {
189         return false;
190     }
191 
192     @Override
193     protected final int startApplication(Product ctx, final File app, final File homeDir, Map<String, String> properties) throws MojoExecutionException
194     {
195         log.info("Starting " + ctx.getInstanceId() + " on ports "
196                 + ctx.getHttpPort() + " (http) and " + controlPort(ctx.getHttpPort()) + " (control)");
197 
198         AntJavaExecutorThread thread;
199         try
200         {
201             thread = execFishEyeCmd("run", ctx);
202         }
203         catch (Exception e)
204         {
205             throw new MojoExecutionException("Error starting fisheye.", e);
206         }
207 
208         waitForFishEyeToStart(ctx, thread);
209 
210         return ctx.getHttpPort();
211     }
212 
213     private void waitForFishEyeToStart(Product ctx, AntJavaExecutorThread thread) throws MojoExecutionException
214     {
215         boolean connected = false;
216         int waited = 0;
217         while (!connected)
218         {
219             try
220             {
221                 Thread.sleep(STARTUP_CHECK_DELAY);
222             }
223             catch (InterruptedException e)
224             {
225                 // ignore
226             }
227             try
228             {
229                 new Socket("localhost", ctx.getHttpPort()).close();
230                 connected = true;
231             }
232             catch (IOException e)
233             {
234                 // ignore
235             }
236 
237             if (thread.isFinished())
238             {
239                 throw new MojoExecutionException("Fisheye failed to start.", thread.getBuildException());
240             }
241 
242             if (waited++ * STARTUP_CHECK_DELAY > ctx.getStartupTimeout())
243             {
244                 throw new MojoExecutionException("FishEye took longer than " + ctx.getStartupTimeout() + "ms to start!");
245             }
246         }
247     }
248 
249     private void waitForFishEyeToStop(Product ctx) throws MojoExecutionException
250     {
251         boolean connected = true;
252         int waited = 0;
253         while (connected)
254         {
255             try
256             {
257                 Thread.sleep(STARTUP_CHECK_DELAY);
258             }
259             catch (InterruptedException e)
260             {
261                 // ignore
262             }
263             try
264             {
265                 new Socket("localhost", ctx.getHttpPort()).close();
266             }
267             catch (IOException e)
268             {
269                 connected = false;
270             }
271 
272             if (waited++ * STARTUP_CHECK_DELAY > ctx.getShutdownTimeout())
273             {
274                 throw new MojoExecutionException("FishEye took longer than " + ctx.getShutdownTimeout() + "ms to stop!");
275             }
276         }
277     }
278 
279     private AntJavaExecutorThread execFishEyeCmd(String bootCommand, final Product ctx) throws MojoExecutionException
280     {
281         final Map<String, String> properties = mergeSystemProperties(ctx);
282 
283         Java java = javaTaskFactory.newJavaTask(
284                 output(ctx.getOutput()).
285                 systemProperties(properties).
286                 jvmArgs(ctx.getJvmArgs()));
287 
288         addOverridesToJavaTask(ctx, java);
289 
290         Path classpath = java.createClasspath();
291         classpath.createPathElement().setLocation(new File(getAppDirectory(ctx), "fisheyeboot.jar"));
292 
293         java.setClassname("com.cenqua.fisheye.FishEyeCtl");
294 
295         java.createArg().setValue(bootCommand);
296 
297         AntJavaExecutorThread javaThread = new AntJavaExecutorThread(java);
298         javaThread.start();
299 
300         return javaThread;
301     }
302 
303     /**
304      * Add overrides to the Java task before it gets launched. The Studio version of FishEye-Crucible
305      * needs to fork the process here.
306      *
307      * @param ctx
308      * @param java
309      */
310     protected void addOverridesToJavaTask(final Product ctx, Java java)
311     {
312         // No override necessary
313     }
314 
315     protected File getBuildDirectory()
316     {
317         return new File(project.getBuild().getDirectory());
318     }
319 
320     private String siteUrl(Product ctx)
321     {
322         return "http://" + ctx.getServer() + ":" + ctx.getHttpPort() + ctx.getContextPath();
323     }
324 
325     /**
326      * The control port is the httpPort with a "1" appended to it //todo doc this
327      */
328     public static int controlPort(int httpPort)
329     {
330         return httpPort * 10 + 1;
331     }
332 
333     private static class FeCruPluginProvider extends AbstractPluginProvider
334     {
335 
336         @Override
337         protected Collection<ProductArtifact> getSalArtifacts(String salVersion)
338         {
339             return Arrays.asList(
340                     new ProductArtifact("com.atlassian.sal", "sal-api", salVersion),
341                     new ProductArtifact("com.atlassian.sal", "sal-fisheye-plugin", salVersion)
342             );
343         }
344     }
345 
346     @Override
347     public void cleanupProductHomeForZip(Product product, File homeDirectory) throws MojoExecutionException, IOException
348     {
349         super.cleanupProductHomeForZip(product, homeDirectory);
350         FileUtils.deleteQuietly(new File(homeDirectory, "var/log"));
351         FileUtils.deleteQuietly(new File(homeDirectory, "var/plugins"));
352         FileUtils.deleteQuietly(new File(homeDirectory, "cache/plugins"));
353     }
354 }