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