View Javadoc

1   /**
2    *    Copyright 2012 meltmedia
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *        http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package com.meltmedia.cadmium.cli;
17  
18  import java.io.IOException;
19  import java.net.URI;
20  import java.net.URISyntaxException;
21  import java.util.List;
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  
25  import org.apache.http.HttpEntity;
26  import org.apache.http.HttpResponse;
27  import org.apache.http.HttpStatus;
28  import org.apache.http.client.ClientProtocolException;
29  import org.apache.http.client.HttpClient;
30  import org.apache.http.client.methods.HttpGet;
31  import org.apache.http.client.utils.URIBuilder;
32  import org.apache.http.impl.client.DefaultHttpClient;
33  import org.apache.http.util.EntityUtils;
34  
35  import com.beust.jcommander.Parameter;
36  import com.beust.jcommander.Parameters;
37  import com.google.gson.Gson;
38  import com.google.gson.reflect.TypeToken;
39  import com.meltmedia.cadmium.core.history.HistoryEntry;
40  
41  /**
42   * Retrieves and displays the history of the requested Cadmium site in a human readable format.
43   * 
44   * @author John McEntire
45   *
46   */
47  @Parameters(commandDescription = "Lists history for a given site in human readable form.", separators="=")
48  public class HistoryCommand extends AbstractAuthorizedOnly implements CliCommand {
49    private static final Pattern URI_PATTERN = Pattern.compile("^(http[s]{0,1}://.*)$");
50  
51    @Parameter(names="-n", description="Limits number of history items returned.", required=false)
52    private Integer limit = -1;
53    
54    @Parameter(names="-r", description="Filters out non revertible history items from list.", required=false)
55    private boolean filter = false;
56    
57    @Parameter(description="Site", required=true)
58    private List<String> site;
59    
60    public void execute() throws Exception {
61      String siteUri = getSecureBaseUrl(site.get(0));
62      Matcher siteMatcher = URI_PATTERN.matcher(siteUri);
63      if(siteMatcher.matches()) {
64        siteUri = siteMatcher.group(1);
65        
66        System.out.println("Showing history for "+siteUri+":");
67        
68        List<HistoryEntry> history = getHistory(siteUri, limit, filter, token);
69              
70        displayHistory(history, false, null);
71      } else {
72        System.err.println("Invalid value for site parameter!");
73        System.exit(1);
74      }
75    }
76  
77    /**
78     * Displays a list of {@link HistoryEntry} objects.
79     * @param history The list of entries to display.
80     * @param filter If true then only revertable history entries will be displayed.
81     * @param limitHistory If set this will limit the number of history entries to display.
82     */
83    public static void displayHistory(List<HistoryEntry> history, boolean filter, Integer limitHistory) {
84      if(history != null && history.size() > 0) {
85        System.console().format("%7s|%7s|%12s|%7s|%14s|%52s|%18s|%42s|%24s|%6s|%6s|%6s|%6s\n", "Index", "Type", "Date", "Time", "User", "Repository", "Branch", "Revision", "Time Live", "Maint", "Revert", "Done", "Fail");
86        for(int i=0; i<218; i++) {
87          System.out.print("-");
88        }
89        System.out.println();
90        boolean showing = false;
91        for(HistoryEntry entry : history) {
92          if(!filter || entry.isRevertible()) {
93            if(limitHistory == null || limitHistory-- > 0) {
94              showing = true;
95              System.console().format("%7d|%7s|%4tm/%<2td/%<4tY|%<4tH:%<2tM|%14s|%52s|%18s|%42s|%24s|%6b|%6b|%6s|%6s\n",
96                  entry.getIndex(),
97                  entry.getType(),
98                  entry.getTimestamp(),
99                  entry.getOpenId(),
100                 entry.getRepoUrl(),
101                 entry.getBranch(),
102                 entry.getRevision(),
103                 formatTimeLive(entry.getTimeLive() == 0 ? System.currentTimeMillis() - entry.getTimestamp().getTime() : entry.getTimeLive()),
104                 entry.isMaintenance(),
105                 entry.isRevertible(),
106                 entry.isFinished(),
107                 entry.isFailed());
108             printComments(entry.getComment());
109           }
110         }
111       }
112       if(!showing) {
113         System.out.println("No history to show");
114       }
115     } else {
116       System.out.println("No history to show");
117     }
118   }
119   
120   /**
121    * Waits until a timeout is reached or a token shows up in the history of a site as finished or failed.
122    * 
123    * @param siteUri The uri to a cadmium site.
124    * @param token The token that represents a history event to wait for.
125    * @param since A timestamp to pass on the the cadmium site to set a limit on how far back to check the history for a token.
126    * @param timeout The timeout in milliseconds to wait for if the token never shows up or fails in the sites log.
127    * @throws Exception
128    */
129   public static void waitForToken(String siteUri, String token, Long since, Long timeout) throws Exception {
130 
131     if(!siteUri.endsWith("/system/history")) {
132       siteUri += "/system/history";
133     }
134     siteUri += "/" + token;
135     if(since != null) {
136       siteUri += "/" + since;
137     }
138     
139     HttpClient httpClient = setTrustAllSSLCerts(new DefaultHttpClient());
140     HttpGet get = new HttpGet(siteUri);
141     Long currentTime = System.currentTimeMillis();
142     Long timeoutTime = currentTime + timeout;
143     do {
144       currentTime = System.currentTimeMillis();
145       
146       HttpResponse resp = httpClient.execute(get);
147       if(resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
148         String response = EntityUtils.toString(resp.getEntity());
149         if(response != null && response.trim().equalsIgnoreCase("true")) {
150           return;
151         } else {
152           Thread.sleep(1000l);
153         }
154       } else {
155         String errorResponse = EntityUtils.toString(resp.getEntity());
156         if(errorResponse != null) {
157           throw new Exception(errorResponse.trim());
158         } else {
159           throw new Exception("Command failed!");
160         }
161       }
162     } while(currentTime < timeoutTime);
163     if(currentTime >= timeoutTime) {
164       throw new Exception("Timed out waiting for command to complete!");
165     }
166   }
167 
168   /**
169    * Retrieves the history of a Cadmium site.
170    * 
171    * @param siteUri The uri of a cadmium site.
172    * @param limit The maximum number of history entries to retrieve or if set to -1 tells the site to retrieve all history.
173    * @param filter If true filters out the non revertable history entries.
174    * @param token The Github API token to pass to the Cadmium site for authentication.
175    * 
176    * @return A list of {@link HistoryEntry} Objects that are populated with the history returned from the Cadmium site.
177    * 
178    * @throws URISyntaxException
179    * @throws IOException
180    * @throws ClientProtocolException
181    * @throws Exception
182    */
183   public static List<HistoryEntry> getHistory(String siteUri, int limit, boolean filter, String token)
184       throws URISyntaxException, IOException, ClientProtocolException, Exception {
185 
186     if(!siteUri.endsWith("/system/history")) {
187       siteUri += "/system/history";
188     }
189     
190     List<HistoryEntry> history = null;
191     
192     HttpClient httpClient = setTrustAllSSLCerts(new DefaultHttpClient());
193     HttpGet get = null;
194     try {
195       URIBuilder uriBuilder = new URIBuilder(siteUri);
196       if(limit > 0) {
197         uriBuilder.addParameter("limit", limit+"");
198       }
199       if(filter) {
200         uriBuilder.addParameter("filter", filter+"");
201       }
202       URI uri = uriBuilder.build();
203       get = new HttpGet(uri);
204       addAuthHeader(token, get);
205       
206       HttpResponse resp = httpClient.execute(get);
207       if(resp.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
208         HttpEntity entity = resp.getEntity();
209         if(entity.getContentType().getValue().equals("application/json")) {
210           String responseContent = EntityUtils.toString(entity);
211           
212           history = new Gson().fromJson(responseContent, new TypeToken<List<HistoryEntry>>() {}.getType());
213         } else {
214           System.err.println("Invalid response content type ["+entity.getContentType().getValue()+"]");
215           System.exit(1);
216         }
217       } else {
218         System.err.println("Request failed due to a ["+resp.getStatusLine().getStatusCode()+":"+resp.getStatusLine().getReasonPhrase()+"] response from the remote server.");
219         System.exit(1);
220       }
221     } finally {
222       if(get != null) {
223         get.releaseConnection();
224       }
225     }
226     return history;
227   }
228 
229   /**
230    * Helper method to format comments to standard out.
231    * 
232    * @param comment
233    */
234   private static void printComments(String comment) {
235     int index = 0;
236     int nextIndex = 154;
237     while(index < comment.length()) {
238       nextIndex = nextIndex <= comment.length() ? nextIndex : comment.length();
239       String commentSegment = comment.substring(index, nextIndex);
240       int lastSpace = commentSegment.lastIndexOf(' ');
241       int lastNewLine = commentSegment.indexOf('\n');
242       char lastChar = ' ';
243       if(nextIndex < comment.length() ) {
244         lastChar = comment.charAt(nextIndex);
245       }
246       if(lastNewLine > 0) {
247         nextIndex = index + lastNewLine;
248         commentSegment = comment.substring(index, nextIndex);
249       } else
250       if(Character.isWhitespace(lastChar)) {
251         
252       } else
253       if(lastSpace > 0) {
254         nextIndex = index + lastSpace;
255         commentSegment = comment.substring(index, nextIndex);
256       }
257       System.out.println("  " + commentSegment);
258       index = nextIndex;
259       if(lastNewLine > 0 || lastSpace > 0) {
260         index++;
261       }
262       nextIndex = index + 154;
263     }
264   }
265 
266   /**
267    * Helper method to format a timestamp.
268    * 
269    * @param timeLive
270    * @return
271    */
272   private static String formatTimeLive(long timeLive) {
273     String timeString = "ms";
274     timeString = (timeLive % 1000) + timeString;
275     timeLive = timeLive / 1000;
276     if(timeLive > 0) {
277       timeString = (timeLive % 60) + "s" + timeString;
278       timeLive = timeLive / 60;
279       if(timeLive > 0) {
280         timeString = (timeLive % 60) + "m" + timeString;
281         timeLive = timeLive / 60;
282         if(timeLive > 0) {
283           timeString = (timeLive % 24) + "h" + timeString;
284           timeLive = timeLive / 24;
285           if(timeLive > 0) {
286             timeString = (timeLive) + "d" + timeString;
287           }
288         }
289       }
290     }
291     return timeString;
292   }
293 
294   @Override
295   public String getCommandName() {
296     return "history";
297   }
298   
299 }