001/*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License").  You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.quicksetup.util;
028
029import static org.opends.messages.QuickSetupMessages.*;
030
031import java.io.UnsupportedEncodingException;
032import java.net.URLDecoder;
033import java.net.URLEncoder;
034
035import org.forgerock.i18n.LocalizableMessage;
036import org.forgerock.i18n.LocalizableMessageBuilder;
037import org.forgerock.i18n.slf4j.LocalizedLogger;
038import org.opends.quicksetup.Constants;
039import org.opends.quicksetup.ui.UIFactory;
040
041/**
042 * This is an implementation of the ProgressMessageFormatter class that
043 * provides format in HTML.
044 */
045public class HtmlProgressMessageFormatter implements ProgressMessageFormatter
046{
047  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
048
049  private LocalizableMessage doneHtml;
050  private LocalizableMessage errorHtml;
051
052  /** The constant used to separate parameters in an URL. */
053  private static final String PARAM_SEPARATOR = "&&&&";
054  /** The space in HTML. */
055  private static final LocalizableMessage SPACE = LocalizableMessage.raw(" ");
056
057  /**
058   * The line break.
059   * The extra char is necessary because of bug:
060   * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4988885
061   */
062   private static final LocalizableMessage LINE_BREAK=
063     LocalizableMessage.raw("
"+Constants.HTML_LINE_BREAK);
064
065   private static final LocalizableMessage TAB = new LocalizableMessageBuilder(SPACE)
066   .append(SPACE)
067   .append(SPACE)
068   .append(SPACE)
069   .append(SPACE)
070   .toMessage();
071
072  /**
073   * Returns the HTML representation of the text without providing any style.
074   * @param text the source text from which we want to get the HTML
075   * representation
076   * @return the HTML representation for the given text.
077   */
078  @Override
079  public LocalizableMessage getFormattedText(LocalizableMessage text)
080  {
081    return LocalizableMessage.raw(Utils.getHtml(String.valueOf(text)));
082  }
083
084  /**
085   * Returns the HTML representation of the text that is the summary of the
086   * installation process (the one that goes in the UI next to the progress
087   * bar).
088   * @param text the source text from which we want to get the formatted
089   * representation
090   * @return the HTML representation of the summary for the given text.
091   */
092  @Override
093  public LocalizableMessage getFormattedSummary(LocalizableMessage text)
094  {
095    return new LocalizableMessageBuilder("<html>")
096            .append(UIFactory.applyFontToHtml(
097                    String.valueOf(text), UIFactory.PROGRESS_FONT))
098            .toMessage();
099  }
100
101  /**
102   * Returns the HTML representation of an error for a given text.
103   * @param text the source text from which we want to get the HTML
104   * representation
105   * @param applyMargin specifies whether we apply a margin or not to the
106   * resulting HTML.
107   * @return the HTML representation of an error for the given text.
108   */
109  @Override
110  public LocalizableMessage getFormattedError(LocalizableMessage text, boolean applyMargin)
111  {
112    String html;
113    if (!Utils.containsHtml(String.valueOf(text))) {
114      html = UIFactory.getIconHtml(UIFactory.IconType.ERROR_LARGE)
115          + SPACE
116          + SPACE
117          + UIFactory.applyFontToHtml(Utils.getHtml(String.valueOf(text)),
118              UIFactory.PROGRESS_ERROR_FONT);
119    } else {
120      html =
121          UIFactory.getIconHtml(UIFactory.IconType.ERROR_LARGE) + SPACE
122          + SPACE + UIFactory.applyFontToHtml(
123                  String.valueOf(text), UIFactory.PROGRESS_FONT);
124    }
125
126    String result = UIFactory.applyErrorBackgroundToHtml(html);
127    if (applyMargin)
128    {
129      result =
130          UIFactory.applyMargin(result,
131              UIFactory.TOP_INSET_ERROR_MESSAGE, 0, 0, 0);
132    }
133    return LocalizableMessage.raw(result);
134  }
135
136  /**
137   * Returns the HTML representation of a warning for a given text.
138   * @param text the source text from which we want to get the HTML
139   * representation
140   * @param applyMargin specifies whether we apply a margin or not to the
141   * resulting HTML.
142   * @return the HTML representation of a warning for the given text.
143   */
144  @Override
145  public LocalizableMessage getFormattedWarning(LocalizableMessage text, boolean applyMargin)
146  {
147    String html;
148    if (!Utils.containsHtml(String.valueOf(text))) {
149      html =
150        UIFactory.getIconHtml(UIFactory.IconType.WARNING_LARGE)
151            + SPACE
152            + SPACE
153            + UIFactory.applyFontToHtml(Utils.getHtml(String.valueOf(text)),
154                UIFactory.PROGRESS_WARNING_FONT);
155    } else {
156      html =
157          UIFactory.getIconHtml(UIFactory.IconType.WARNING_LARGE) + SPACE
158          + SPACE + UIFactory.applyFontToHtml(
159                  String.valueOf(text), UIFactory.PROGRESS_FONT);
160    }
161
162    String result = UIFactory.applyWarningBackgroundToHtml(html);
163    if (applyMargin)
164    {
165      result =
166          UIFactory.applyMargin(result,
167              UIFactory.TOP_INSET_ERROR_MESSAGE, 0, 0, 0);
168    }
169    return LocalizableMessage.raw(result);
170  }
171
172  /**
173   * Returns the HTML representation of a success message for a given text.
174   * @param text the source text from which we want to get the HTML
175   * representation
176   * @return the HTML representation of a success message for the given text.
177   */
178  @Override
179  public LocalizableMessage getFormattedSuccess(LocalizableMessage text)
180  {
181    // Note: the text we get already is in HTML form
182    String html =
183        UIFactory.getIconHtml(UIFactory.IconType.INFORMATION_LARGE) + SPACE
184        + SPACE + UIFactory.applyFontToHtml(String.valueOf(text),
185                UIFactory.PROGRESS_FONT);
186
187    return LocalizableMessage.raw(UIFactory.applySuccessfulBackgroundToHtml(html));
188  }
189
190  /**
191   * Returns the HTML representation of a log error message for a given
192   * text.
193   * @param text the source text from which we want to get the HTML
194   * representation
195   * @return the HTML representation of a log error message for the given
196   * text.
197   */
198  @Override
199  public LocalizableMessage getFormattedLogError(LocalizableMessage text)
200  {
201    String html = Utils.getHtml(String.valueOf(text));
202    return LocalizableMessage.raw(UIFactory.applyFontToHtml(html,
203        UIFactory.PROGRESS_LOG_ERROR_FONT));
204  }
205
206
207  /**
208   * Returns the HTML representation of a log message for a given text.
209   * @param text the source text from which we want to get the HTML
210   * representation
211   * @return the HTML representation of a log message for the given text.
212   */
213  @Override
214  public LocalizableMessage getFormattedLog(LocalizableMessage text)
215  {
216    String html = Utils.getHtml(String.valueOf(text));
217    return LocalizableMessage.raw(UIFactory.applyFontToHtml(html,
218            UIFactory.PROGRESS_LOG_FONT));
219  }
220
221  /**
222   * Returns the HTML representation of the 'Done' text string.
223   * @return the HTML representation of the 'Done' text string.
224   */
225  @Override
226  public LocalizableMessage getFormattedDone()
227  {
228    if (doneHtml == null)
229    {
230      String html = Utils.getHtml(INFO_PROGRESS_DONE.get().toString());
231      doneHtml = LocalizableMessage.raw(UIFactory.applyFontToHtml(html,
232          UIFactory.PROGRESS_DONE_FONT));
233    }
234    return LocalizableMessage.raw(doneHtml);
235  }
236
237  /**
238   * Returns the HTML representation of the 'Error' text string.
239   * @return the HTML representation of the 'Error' text string.
240   */
241  @Override
242  public LocalizableMessage getFormattedError() {
243    if (errorHtml == null)
244    {
245      String html = Utils.getHtml(INFO_PROGRESS_ERROR.get().toString());
246      errorHtml = LocalizableMessage.raw(UIFactory.applyFontToHtml(html,
247          UIFactory.PROGRESS_ERROR_FONT));
248    }
249    return LocalizableMessage.raw(errorHtml);
250  }
251
252  /**
253   * Returns the HTML representation of the argument text to which we add
254   * points.  For instance if we pass as argument 'Configuring Server' the
255   * return value will be 'Configuring Server <B>.....</B>'.
256   * @param text the String to which add points.
257   * @return the HTML representation of the '.....' text string.
258   */
259  @Override
260  public LocalizableMessage getFormattedWithPoints(LocalizableMessage text)
261  {
262    String html = Utils.getHtml(String.valueOf(text));
263    String points = SPACE +
264            Utils.getHtml(INFO_PROGRESS_POINTS.get().toString()) + SPACE;
265
266    LocalizableMessageBuilder buf = new LocalizableMessageBuilder();
267    buf.append(UIFactory.applyFontToHtml(html, UIFactory.PROGRESS_FONT))
268        .append(
269            UIFactory.applyFontToHtml(points, UIFactory.PROGRESS_POINTS_FONT));
270
271    return buf.toMessage();
272  }
273
274  /**
275   * Returns the formatted representation of a point.
276   * @return the formatted representation of the '.' text string.
277   */
278  @Override
279  public LocalizableMessage getFormattedPoint()
280  {
281    return LocalizableMessage.raw(UIFactory.applyFontToHtml(".",
282        UIFactory.PROGRESS_POINTS_FONT));
283  }
284
285  /**
286   * Returns the formatted representation of a space.
287   * @return the formatted representation of the ' ' text string.
288   */
289  @Override
290  public LocalizableMessage getSpace()
291  {
292    return LocalizableMessage.raw(SPACE);
293  }
294
295  /**
296   * Returns the formatted representation of a progress message for a given
297   * text.
298   * @param text the source text from which we want to get the formatted
299   * representation
300   * @return the formatted representation of a progress message for the given
301   * text.
302   */
303  @Override
304  public LocalizableMessage getFormattedProgress(LocalizableMessage text)
305  {
306    return LocalizableMessage.raw(UIFactory.applyFontToHtml(
307        Utils.getHtml(String.valueOf(text)),
308        UIFactory.PROGRESS_FONT));
309  }
310
311  /**
312   * Returns the HTML representation of an error message for a given throwable.
313   * This method applies a margin if the applyMargin parameter is
314   * <CODE>true</CODE>.
315   * @param t the throwable.
316   * @param applyMargin specifies whether we apply a margin or not to the
317   * resulting HTML.
318   * @return the HTML representation of an error message for the given
319   * exception.
320   */
321  @Override
322  public LocalizableMessage getFormattedError(Throwable t, boolean applyMargin)
323  {
324    String openDiv = "<div style=\"margin-left:5px; margin-top:10px\">";
325    String hideText =
326        UIFactory.applyFontToHtml(INFO_HIDE_EXCEPTION_DETAILS.get().toString(),
327            UIFactory.PROGRESS_FONT);
328    String showText =
329        UIFactory.applyFontToHtml(INFO_SHOW_EXCEPTION_DETAILS.get().toString(),
330            UIFactory.PROGRESS_FONT);
331    String closeDiv = "</div>";
332
333    StringBuilder stackBuf = new StringBuilder();
334    stackBuf.append(getHtmlStack(t));
335    Throwable root = t.getCause();
336    while (root != null)
337    {
338      stackBuf.append(Utils.getHtml(INFO_EXCEPTION_ROOT_CAUSE.get().toString()))
339              .append(getLineBreak());
340      stackBuf.append(getHtmlStack(root));
341      root = root.getCause();
342    }
343    String stackText =
344        UIFactory.applyFontToHtml(stackBuf.toString(), UIFactory.STACK_FONT);
345
346    StringBuilder buf = new StringBuilder();
347
348    String msg = t.getMessage();
349    if (msg != null)
350    {
351      buf.append(UIFactory.applyFontToHtml(Utils.getHtml(t.getMessage()),
352              UIFactory.PROGRESS_ERROR_FONT)).append(getLineBreak());
353    } else
354    {
355      buf.append(t).append(getLineBreak());
356    }
357    buf.append(getErrorWithStackHtml(openDiv, hideText, showText, stackText,
358        closeDiv, false));
359
360    String html = UIFactory.getIconHtml(UIFactory.IconType.ERROR_LARGE) + SPACE + SPACE + buf;
361
362    String result;
363    if (applyMargin)
364    {
365      result =
366          UIFactory.applyMargin(UIFactory.applyErrorBackgroundToHtml(html),
367              UIFactory.TOP_INSET_ERROR_MESSAGE, 0, 0, 0);
368    } else
369    {
370      result = UIFactory.applyErrorBackgroundToHtml(html);
371    }
372    return LocalizableMessage.raw(result);
373  }
374
375  /**
376   * Returns the line break in HTML.
377   * @return the line break in HTML.
378   */
379  @Override
380  public LocalizableMessage getLineBreak()
381  {
382    return LINE_BREAK;
383  }
384
385  /**
386   * Returns the tab in HTML.
387   * @return the tab in HTML.
388   */
389  @Override
390  public LocalizableMessage getTab()
391  {
392    return TAB;
393  }
394
395  /**
396   * Returns the task separator in HTML.
397   * @return the task separator in HTML.
398   */
399  @Override
400  public LocalizableMessage getTaskSeparator()
401  {
402    return LocalizableMessage.raw(UIFactory.HTML_SEPARATOR);
403  }
404
405  /**
406   * Returns the log HTML representation after the user has clicked on a url.
407   *
408   * @see HtmlProgressMessageFormatter#getErrorWithStackHtml
409   * @param url that has been clicked
410   * @param lastText the HTML representation of the log before clicking on the
411   * url.
412   * @return the log HTML representation after the user has clicked on a url.
413   */
414  @Override
415  public LocalizableMessage getFormattedAfterUrlClick(String url, LocalizableMessage lastText)
416  {
417    String urlText = getErrorWithStackHtml(url, false);
418    String newUrlText = getErrorWithStackHtml(url, true);
419    String lastTextStr = String.valueOf(lastText);
420
421    int index = lastTextStr.indexOf(urlText);
422    if (index == -1)
423    {
424      logger.trace("lastText: " + lastText +
425              "does not contain: " + urlText);
426    } else
427    {
428      lastTextStr =
429          lastTextStr.substring(0, index) + newUrlText
430              + lastTextStr.substring(index + urlText.length());
431    }
432    return LocalizableMessage.raw(lastTextStr);
433  }
434
435  /**
436   * Returns a HTML representation of the stack trace of a Throwable object.
437   * @param ex the throwable object from which we want to obtain the stack
438   * trace HTML representation.
439   * @return a HTML representation of the stack trace of a Throwable object.
440   */
441  private String getHtmlStack(Throwable ex)
442  {
443    StringBuilder buf = new StringBuilder();
444    buf.append(SPACE)
445    .append(SPACE)
446    .append(SPACE)
447    .append(SPACE)
448    .append(SPACE)
449    .append(SPACE)
450    .append(SPACE)
451    .append(SPACE)
452    .append(SPACE)
453    .append(SPACE)
454    .append(Utils.getHtml(ex.toString()))
455    .append(getLineBreak());
456    StackTraceElement[] stack = ex.getStackTrace();
457    for (StackTraceElement aStack : stack) {
458      buf.append(SPACE)
459              .append(SPACE)
460              .append(SPACE)
461              .append(SPACE)
462              .append(SPACE)
463              .append(SPACE)
464              .append(SPACE)
465              .append(SPACE)
466              .append(SPACE)
467              .append(SPACE)
468              .append(Utils.getHtml(aStack.toString()))
469              .append(getLineBreak());
470    }
471    return buf.toString();
472  }
473
474  /**
475   * Returns the HTML representation of an exception in the
476   * progress log.<BR>
477   * We can have something of type:<BR><BR>
478   *
479   * An error occurred.  java.io.IOException could not connect to server.<BR>
480   * <A HREF="">Show Details</A>
481   *
482   * When the user clicks on 'Show Details' the whole stack will be displayed.
483   *
484   * An error occurred.  java.io.IOException could not connect to server.<BR>
485   * <A HREF="">Hide Details</A><BR>
486   * ... And here comes all the stack trace representation<BR>
487   *
488   *
489   * As the object that listens to this hyperlink events is not here (it is
490   * QuickSetupStepPanel) we must include all the information somewhere.  The
491   * chosen solution is to include everything in the URL using parameters.
492   * This everything consists of:
493   * The open div tag for the text.
494   * The text that we display when we do not display the exception.
495   * The text that we display when we display the exception.
496   * The stack trace text.
497   * The closing div.
498   * A boolean informing if we are hiding the exception or not (to know in the
499   * next event what must be displayed).
500   *
501   * @param openDiv the open div tag for the text.
502   * @param hideText the text that we display when we do not display the
503   * exception.
504   * @param showText the text that we display when we display the exception.
505   * @param stackText the stack trace text.
506   * @param closeDiv the closing div.
507   * @param hide a boolean informing if we are hiding the exception or not.
508   * @return the HTML representation of an error message with an stack trace.
509   */
510  private String getErrorWithStackHtml(String openDiv, String hideText,
511      String showText, String stackText, String closeDiv, boolean hide)
512  {
513    StringBuilder buf = new StringBuilder();
514
515    String params =
516        getUrlParams(openDiv, hideText, showText, stackText, closeDiv, hide);
517    try
518    {
519      String text = hide ? hideText : showText;
520      buf.append(openDiv).append("<a href=\"http://")
521              .append(URLEncoder.encode(params, "UTF-8"))
522              .append("\">").append(text).append("</a>");
523      if (hide)
524      {
525        buf.append(getLineBreak()).append(stackText);
526      }
527      buf.append(closeDiv);
528
529    } catch (UnsupportedEncodingException uee)
530    {
531      // Bug
532      throw new IllegalStateException("UTF-8 is not supported ", uee);
533    }
534
535    return buf.toString();
536  }
537
538  /**
539   * Gets the url parameters of the href we construct in getErrorWithStackHtml.
540   * @see HtmlProgressMessageFormatter#getErrorWithStackHtml
541   * @param openDiv the open div tag for the text.
542   * @param hideText the text that we display when we do not display the
543   * exception.
544   * @param showText the text that we display when we display the exception.
545   * @param stackText the stack trace text.
546   * @param closeDiv the closing div.
547   * @param hide a boolean informing if we are hiding the exception or not.
548   * @return the url parameters of the href we construct in getHrefString.
549   */
550  private String getUrlParams(String openDiv, String hideText,
551      String showText, String stackText, String closeDiv, boolean hide)
552  {
553    StringBuilder buf = new StringBuilder();
554    buf.append(openDiv).append(PARAM_SEPARATOR);
555    buf.append(hideText).append(PARAM_SEPARATOR);
556    buf.append(showText).append(PARAM_SEPARATOR);
557    buf.append(stackText).append(PARAM_SEPARATOR);
558    buf.append(closeDiv).append(PARAM_SEPARATOR);
559    buf.append(hide);
560    return buf.toString();
561  }
562
563  /**
564   * Returns the HTML representation of an exception in the
565   * progress log for a given url.
566   * @param url the url containing all the information required to retrieve
567   * the HTML representation.
568   * @param inverse indicates whether we want to 'inverse' the representation
569   * or not.  For instance if the url specifies that the stack is being hidden
570   * and this parameter is <CODE>true</CODE> the resulting HTML will display
571   * the stack.
572   * @return the HTML representation of an exception in the progress log for a
573   * given url.
574   */
575  private String getErrorWithStackHtml(String url, boolean inverse)
576  {
577    String p = url.substring("http://".length());
578    try
579    {
580      p = URLDecoder.decode(p, "UTF-8");
581    } catch (UnsupportedEncodingException uee)
582    {
583      // Bug
584      throw new IllegalStateException("UTF-8 is not supported ", uee);
585    }
586    String params[] = p.split(PARAM_SEPARATOR);
587    int i = 0;
588    String openDiv = params[i++];
589    String hideText = params[i++];
590    String showText = params[i++];
591    String stackText = params[i++];
592    String closeDiv = params[i++];
593    boolean isHide = Boolean.parseBoolean(params[i]);
594
595    if (isHide)
596    {
597      return getErrorWithStackHtml(openDiv, hideText, showText, stackText,
598          closeDiv, !inverse);
599    } else
600    {
601      return getErrorWithStackHtml(openDiv, hideText, showText, stackText,
602          closeDiv, inverse);
603    }
604  }
605
606}
607