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