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