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