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 org.apache.commons.io.FileUtils;
19  import org.apache.maven.plugin.MojoExecutionException;
20  import org.apache.tools.ant.taskdefs.Java;
21  import org.apache.tools.ant.types.Path;
22  
23  import com.atlassian.maven.plugins.amps.MavenGoals;
24  import com.atlassian.maven.plugins.amps.Product;
25  import com.atlassian.maven.plugins.amps.ProductArtifact;
26  import com.atlassian.maven.plugins.amps.util.ConfigFileUtils.Replacement;
27  import com.atlassian.maven.plugins.amps.util.ZipUtils;
28  import com.atlassian.maven.plugins.amps.util.ant.AntJavaExecutorThread;
29  import com.atlassian.maven.plugins.amps.util.ant.JavaTaskFactory;
30  
31  import static com.atlassian.maven.plugins.amps.util.ProjectUtils.createDirectory;
32  import static com.atlassian.maven.plugins.amps.util.ProjectUtils.firstNotNull;
33  
34  public class FeCruProductHandler extends AbstractProductHandler
35  {
36      private static final int STARTUP_CHECK_DELAY = 1000;
37      private static final String FISHEYE_INST = "fisheye.inst";
38  
39      private final JavaTaskFactory javaTaskFactory;
40  
41      public FeCruProductHandler(MavenContext context, MavenGoals goals)
42      {
43          super(context, goals, new FeCruPluginProvider());
44          this.javaTaskFactory = new JavaTaskFactory(log);
45      }
46  
47      public String getId()
48      {
49          return ProductHandlerFactory.FECRU;
50      }
51  
52      protected ProductArtifact getArtifact()
53      {
54          return new ProductArtifact("com.atlassian.crucible", "atlassian-crucible", "RELEASE");
55      }
56  
57      public int getDefaultHttpPort()
58      {
59          return 3990;
60      }
61  
62      public final void stop(Product ctx) throws MojoExecutionException
63      {
64          log.info("Stopping " + ctx.getInstanceId() + " on ports "
65                  + ctx.getHttpPort() + " (http) and " + controlPort(ctx.getHttpPort()) + " (control)");
66          try
67          {
68              execFishEyeCmd("stop", ctx);
69          }
70          catch (Exception e)
71          {
72              throw new MojoExecutionException("Failed to stop FishEye/Crucible instance at " + ctx.getServer() + ":" + ctx.getHttpPort());
73          }
74  
75          waitForFishEyeToStop(ctx);
76      }
77  
78      @Override
79      protected void extractProductHomeData(File productHomeZip, File homeDir, Product ctx)
80              throws MojoExecutionException
81      {
82          try
83          {
84              unzip(productHomeZip, homeDir.getPath());
85          }
86          catch (final IOException ex)
87          {
88              throw new MojoExecutionException("Unable to copy home directory", ex);
89          }
90      }
91  
92      @Override
93      public List<Replacement> getReplacements(Product ctx)
94      {
95          List<Replacement> replacements = super.getReplacements(ctx);
96          File homeDirectory = getHomeDirectory(ctx);
97          replacements.add(new Replacement("@CONTROL_BIND@", String.valueOf(controlPort(ctx.getHttpPort()))));
98          replacements.add(new Replacement("@HTTP_BIND@", String.valueOf(ctx.getHttpPort())));
99          replacements.add(new Replacement("@HTTP_CONTEXT@", String.valueOf(ctx.getContextPath()), false));
100         replacements.add(new Replacement("@HOME_DIR@", String.valueOf(homeDirectory.getAbsolutePath())));
101         replacements.add(new Replacement("@SITE_URL@", String.valueOf(siteUrl(ctx))));
102 
103         // Note: Unfortunately, in config.xml, FeCru sometimes uses url encoding.
104         return replacements;
105     }
106 
107     @Override
108     public List<File> getConfigFiles(Product product, File homeDir)
109     {
110         List<File> configFiles = super.getConfigFiles(product, homeDir);
111         configFiles.add(new File(homeDir, "config.xml"));
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         if (ctx.getJvmArgs() == null)
196         {
197             ctx.setJvmArgs("-Xmx512m -XX:MaxPermSize=160m");
198         }
199 
200         log.info("Starting " + ctx.getInstanceId() + " on ports "
201                 + ctx.getHttpPort() + " (http) and " + controlPort(ctx.getHttpPort()) + " (control)");
202 
203         AntJavaExecutorThread thread;
204         try
205         {
206             thread = execFishEyeCmd("run", ctx);
207         }
208         catch (Exception e)
209         {
210             throw new MojoExecutionException("Error starting fisheye.", e);
211         }
212 
213         waitForFishEyeToStart(ctx, thread);
214 
215         return ctx.getHttpPort();
216     }
217 
218     private void waitForFishEyeToStart(Product ctx, AntJavaExecutorThread thread) throws MojoExecutionException
219     {
220         boolean connected = false;
221         int waited = 0;
222         while (!connected)
223         {
224             try
225             {
226                 Thread.sleep(STARTUP_CHECK_DELAY);
227             }
228             catch (InterruptedException e)
229             {
230                 // ignore
231             }
232             try
233             {
234                 new Socket("localhost", ctx.getHttpPort()).close();
235                 connected = true;
236             }
237             catch (IOException e)
238             {
239                 // ignore
240             }
241 
242             if (thread.isFinished())
243             {
244                 throw new MojoExecutionException("Fisheye failed to start.", thread.getBuildException());
245             }
246 
247             if (waited++ * STARTUP_CHECK_DELAY > ctx.getStartupTimeout())
248             {
249                 throw new MojoExecutionException("FishEye took longer than " + ctx.getStartupTimeout() + "ms to start!");
250             }
251         }
252     }
253 
254     private void waitForFishEyeToStop(Product ctx) throws MojoExecutionException
255     {
256         boolean connected = true;
257         int waited = 0;
258         while (connected)
259         {
260             try
261             {
262                 Thread.sleep(STARTUP_CHECK_DELAY);
263             }
264             catch (InterruptedException e)
265             {
266                 // ignore
267             }
268             try
269             {
270                 new Socket("localhost", ctx.getHttpPort()).close();
271             }
272             catch (IOException e)
273             {
274                 connected = false;
275             }
276 
277             if (waited++ * STARTUP_CHECK_DELAY > ctx.getShutdownTimeout())
278             {
279                 throw new MojoExecutionException("FishEye took longer than " + ctx.getShutdownTimeout() + "ms to stop!");
280             }
281         }
282     }
283 
284     private AntJavaExecutorThread execFishEyeCmd(String bootCommand, final Product ctx) throws MojoExecutionException
285     {
286         final Map<String, String> properties = mergeSystemProperties(ctx);
287 
288         Java java = javaTaskFactory.newJavaTask(
289                 output(ctx.getOutput()).
290                 systemProperties(properties).
291                 jvmArgs(ctx.getJvmArgs()));
292 
293         addOverridesToJavaTask(ctx, java);
294 
295         Path classpath = java.createClasspath();
296         classpath.createPathElement().setLocation(new File(getAppDirectory(ctx), "fisheyeboot.jar"));
297 
298         java.setClassname("com.cenqua.fisheye.FishEyeCtl");
299 
300         java.createArg().setValue(bootCommand);
301 
302         AntJavaExecutorThread javaThread = new AntJavaExecutorThread(java);
303         javaThread.start();
304 
305         return javaThread;
306     }
307 
308     /**
309      * Add overrides to the Java task before it gets launched. The Studio version of FishEye-Crucible
310      * needs to fork the process here.
311      *
312      * @param ctx
313      * @param java
314      */
315     protected void addOverridesToJavaTask(final Product ctx, Java java)
316     {
317         // No override necessary
318     }
319 
320     protected File getBuildDirectory()
321     {
322         return new File(project.getBuild().getDirectory());
323     }
324 
325     private String siteUrl(Product ctx)
326     {
327         return "http://" + ctx.getServer() + ":" + ctx.getHttpPort() + ctx.getContextPath();
328     }
329 
330     /**
331      * The control port is the httpPort with a "1" appended to it //todo doc this
332      */
333     protected int controlPort(int httpPort)
334     {
335         return httpPort * 10 + 1;
336     }
337 
338     private static class FeCruPluginProvider extends AbstractPluginProvider
339     {
340 
341         @Override
342         protected Collection<ProductArtifact> getSalArtifacts(String salVersion)
343         {
344             return Arrays.asList(
345                     new ProductArtifact("com.atlassian.sal", "sal-api", salVersion),
346                     new ProductArtifact("com.atlassian.sal", "sal-fisheye-plugin", salVersion)
347             );
348         }
349     }
350 
351     @Override
352     public void cleanupProductHomeForZip(Product product, File homeDirectory) throws MojoExecutionException, IOException
353     {
354         super.cleanupProductHomeForZip(product, homeDirectory);
355         FileUtils.deleteDirectory(new File(homeDirectory, "var/plugins"));
356         FileUtils.deleteDirectory(new File(homeDirectory, "cache/plugins"));
357     }
358 }