1 19 20 package com.sslexplorer.extensions; 21 22 import java.io.File ; 23 import java.io.IOException ; 24 import java.util.ArrayList ; 25 import java.util.Collection ; 26 import java.util.HashMap ; 27 import java.util.HashSet ; 28 import java.util.Iterator ; 29 import java.util.List ; 30 import java.util.Map ; 31 import java.util.Set ; 32 33 import org.apache.commons.logging.Log; 34 import org.apache.commons.logging.LogFactory; 35 import org.jdom.DataConversionException; 36 import org.jdom.Element; 37 import org.jdom.JDOMException; 38 39 import com.sslexplorer.boot.DefaultPropertyDefinitionCategory; 40 import com.sslexplorer.boot.PropertyClass; 41 import com.sslexplorer.boot.PropertyClassManager; 42 import com.sslexplorer.boot.PropertyDefinition; 43 import com.sslexplorer.boot.PropertyDefinitionCategory; 44 import com.sslexplorer.boot.Util; 45 import com.sslexplorer.boot.XMLPropertyDefinition; 46 import com.sslexplorer.core.CoreMessageResources; 47 import com.sslexplorer.core.CoreServlet; 48 import com.sslexplorer.core.CoreUtil; 49 import com.sslexplorer.properties.attributes.AttributesPropertyClass; 50 import com.sslexplorer.properties.attributes.XMLAttributeDefinition; 51 import com.sslexplorer.security.SessionInfo; 52 53 public class ExtensionDescriptor implements Comparable { 54 55 final static Log log = LogFactory.getLog(ExtensionDescriptor.class); 56 57 HashSet files; 58 HashMap parameters; 59 ExtensionType launcherType; 60 String typeName; 61 CoreMessageResources messageResources; 62 Element element; 63 Map tunnels; 64 String description; 65 String id; 66 String smallIcon; 67 String largeIcon; 68 String name; 69 ExtensionBundle bundle; 70 Element typeElement; 71 Element messagesElement; 72 Element propertyDefinitionsElement; 73 int status; 74 boolean hidden = false; 75 private List <String > messageKeys; 76 private Map <String , PropertyDefinition> propertyDefinitions; 77 private Map <Integer , PropertyDefinitionCategory> propertyDefinitionCategories; 78 79 public ExtensionDescriptor() { 80 this.files = new HashSet (); 81 this.parameters = new HashMap (); 82 this.tunnels = new HashMap (); 83 } 84 85 public String getTypeName() { 86 return typeName; 87 } 88 89 public boolean isOptions() { 90 return parameters.size() > 0; 91 } 92 93 public boolean isHidden() { 94 return hidden; 95 } 96 97 public String getDescription() { 98 return description; 99 } 100 101 public void load(ExtensionBundle bundle, Element element) throws ExtensionException { 102 103 propertyDefinitions = new HashMap <String , PropertyDefinition>(); 104 propertyDefinitionCategories = new HashMap <Integer , PropertyDefinitionCategory>(); 105 messageKeys = new ArrayList <String >(); 106 107 this.bundle = bundle; 108 this.element = element; 109 110 parameters.clear(); 111 tunnels.clear(); 112 files.clear(); 113 114 if (log.isInfoEnabled()) 115 log.info("Loading application descriptor"); 116 117 String rootName = element.getName(); 118 if (rootName.equals("application")) { 119 log.warn("DEPRECATED. Extension descriptor in " + bundle.getFile().getPath() 120 + " should now have <extension> as the root element not <application>"); 121 } 122 123 typeName = element.getAttribute("type").getValue(); 124 if (typeName == null) { 125 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<" + rootName 126 + "> element requires attribute 'type'"); 127 } 128 129 id = element.getAttributeValue("extension"); 130 131 if (id == null) { 132 id = element.getAttributeValue("application"); 133 if (id == null) { 134 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<" + element.getName() 135 + "> element requires attribute 'application'"); 136 } else { 137 log.warn("DEPRECATED. In " + bundle.getFile().getPath() + ", <" + rootName 138 + ">'s 'application' attribute should now be 'extension'"); 139 } 140 } 141 if (log.isInfoEnabled()) 142 log.info("Found extension descriptor " + getId()); 143 if (!id.matches("^[a-zA-Z0-9_-]*$")) { 144 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "<" + element.getName() + "> attribute '" 145 + id + "' may only contain word characters ([a-zA-Z_0-9])."); 146 } 147 148 name = element.getAttributeValue("name"); 149 if (log.isDebugEnabled()) 150 log.debug("Application name is " + name); 151 if (name == null) { 152 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 153 "<application> element requires the attribute 'name'"); 154 } 155 smallIcon = element.getAttributeValue("smallIcon"); 156 largeIcon = element.getAttributeValue("largeIcon"); 157 158 if (element.getAttribute("hidden") != null) { 159 try { 160 hidden = element.getAttribute("hidden").getBooleanValue(); 161 } catch (DataConversionException e) { 162 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, e); 163 } 164 } 165 166 messagesElement = null; 167 typeElement = null; 168 169 for (Iterator it = element.getChildren().iterator(); it.hasNext();) { 170 Element e = (Element) it.next(); 171 if (e.getName().equalsIgnoreCase("description")) { 172 description = e.getText(); 173 } else if (e.getName().equalsIgnoreCase("parameter")) { 174 addParameter(e); 175 } else if (e.getName().equalsIgnoreCase("messages")) { 176 messagesElement = e; 177 } else if (e.getName().equalsIgnoreCase("tunnel")) { 178 verifyTunnel(e); 179 } else if (e.getName().equalsIgnoreCase("files")) { 180 verifyFiles(e); 181 } else if (e.getName().equals("propertyDefinitions")) { 182 propertyDefinitionsElement = e; 183 } else if(e.getName().equals(typeName)){ 184 if(typeElement != null) { 185 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 186 "<" + e.getName() + "> must occur before type specific element."); 187 } 188 typeElement = e; 189 } else { 190 191 } 192 } 193 194 if(Util.isNullOrTrimmedBlank(description)) { 195 throw new ExtensionException(ExtensionException.FAILED_TO_PARSE_DESCRIPTOR, 196 "<extension> element must contain a <description>."); 197 } 198 } 199 200 public void start() throws ExtensionException { 201 206 Map <PropertyClass,Collection <PropertyDefinition>> oldDefinitions = new HashMap <PropertyClass, Collection <PropertyDefinition>>(); 207 Map <PropertyClass,Collection <PropertyDefinitionCategory>> oldCategories = new HashMap <PropertyClass, Collection <PropertyDefinitionCategory>>(); 208 Collection <PropertyClass> oldPropertyClasses = PropertyClassManager.getInstance().getPropertyClasses(); 209 try { 210 for(PropertyClass propertyClass : oldPropertyClasses) { 211 try { 212 propertyClass.store(); 213 } catch (IOException e) { 214 throw new ExtensionException(ExtensionException.INTERNAL_ERROR, e, "Failed to store property class."); 215 } 216 } 217 } 218 catch(ExtensionException ee) { 219 for(PropertyClass propertyClass : oldPropertyClasses) { 220 propertyClass.reset(); 221 } 222 throw ee; 223 } 224 225 try { 226 227 229 try { 230 if (messagesElement != null) { 231 messageResources = CoreServlet.getServlet() 232 .getExtensionStoreResources(); 233 for (Iterator i = messagesElement.getChildren().iterator(); i 234 .hasNext();) { 235 Element el = (Element) i.next(); 236 if (!el.getName().equals("message")) { 237 throw new ExtensionException( 238 ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 239 "<messages> element may only contain <message> elements."); 240 } 241 String key = el.getAttributeValue("key"); 242 if (key == null) { 243 throw new ExtensionException( 244 ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 245 "<message> element must have a key attribute."); 246 } 247 key = "application." + id + "." + key; 248 messageKeys.add(key); 249 messageResources 250 .setMessage(el.getAttributeValue("locale"), 251 key, el.getText()); 252 } 253 messageResources.setMessage("", "application." + getId() 254 + ".name", name); 255 } 256 257 259 if (propertyDefinitionsElement != null) { 260 for (Iterator ci = propertyDefinitionsElement.getChildren() 261 .iterator(); ci.hasNext();) { 262 Element c = (Element) ci.next(); 263 PropertyClassManager pcm = PropertyClassManager 264 .getInstance(); 265 PropertyClass pc = pcm.getPropertyClass(c.getName()); 266 if (pc == null) { 267 throw new ExtensionException( 268 ExtensionException.INTERNAL_ERROR, 269 "No property definition class named " 270 + c.getName()); 271 } 272 for (Iterator ei = c.getChildren().iterator(); ei 273 .hasNext();) { 274 Element ec = (Element) ei.next(); 275 if (ec.getName().equals("definition")) { 276 PropertyDefinition def; 277 if (pc instanceof AttributesPropertyClass) { 278 def = new XMLAttributeDefinition(ec); 279 } else { 280 def = new XMLPropertyDefinition(ec); 281 } 282 propertyDefinitions.put(def.getName(), def); 283 pc.registerPropertyDefinition(def); 284 } else if (ec.getName().equals("category")) { 285 PropertyDefinitionCategory cat = new DefaultPropertyDefinitionCategory( 286 ec.getAttribute("id").getIntValue(), ec 287 .getAttributeValue("bundle"), 288 ec.getAttributeValue("image")); 289 cat.setPropertyClass(pc); 290 if (ec.getAttributeValue("parent") == null) { 291 pc.addPropertyDefinitionCategory(-1, cat); 292 } else { 293 PropertyDefinitionCategory parentCat = pc 294 .getPropertyDefinitionCategory(ec 295 .getAttribute("parent") 296 .getIntValue()); 297 if (parentCat == null) { 298 throw new ExtensionException( 299 ExtensionException.INTERNAL_ERROR, 300 "No parent category for " 301 + cat.getId() 302 + " of " 303 + ec 304 .getAttributeValue("parent")); 305 } 306 pc.addPropertyDefinitionCategory(parentCat 307 .getId(), cat); 308 } 309 propertyDefinitionCategories.put(cat.getId(), 310 cat); 311 } else { 312 throw new ExtensionException( 313 ExtensionException.INTERNAL_ERROR, 314 "Expect child element of <definitions> with child elements of <definition>. Got <" 315 + ec.getName() + ">."); 316 } 317 } 318 } 319 } 320 } catch (JDOMException e) { 321 throw new ExtensionException( 322 ExtensionException.FAILED_TO_PARSE_DESCRIPTOR, e, 323 "Failed to parse descriptor."); 324 } 325 326 328 launcherType = ExtensionTypeManager.getInstance().getExtensionType( 329 typeName, bundle); 330 launcherType.start(this, typeElement); 331 332 for(PropertyClass propertyClass : oldPropertyClasses) { 334 propertyClass.reset(); 335 } 336 337 } catch (Throwable ee) { 338 propertyDefinitionCategories.clear(); 339 for (PropertyClass propertyClass : new ArrayList <PropertyClass>(PropertyClassManager 341 .getInstance().getPropertyClasses())) { 342 343 if(!oldPropertyClasses.contains(propertyClass)) { 345 PropertyClassManager.getInstance().deregisterPropertyClass(propertyClass.getName()); 346 } 347 else { 348 try { 349 propertyClass.restore(); 350 } catch (IOException e) { 351 log.error("Extension failed for some reason, but SSL-Explorer cannot restore the property class. The original exception will follow.", ee); 352 throw new ExtensionException(ExtensionException.INTERNAL_ERROR, e, "Failed to restore property class, this is fatal and no further extensions will correctly load."); 353 } 354 } 355 } 356 if(ee instanceof ExtensionException) { 357 throw (ExtensionException)ee; 358 } 359 else { 360 throw new ExtensionException(ExtensionException.INTERNAL_ERROR, ee); 361 } 362 } 363 launcherType.verifyRequiredElements(); 364 } 365 366 private void addCat(PropertyDefinitionCategory cat, List <PropertyDefinitionCategory> l) { 367 l.add(cat); 368 for(PropertyDefinitionCategory c : cat.getCategories()) { 369 addCat(c, l); 370 } 371 } 372 373 void addCategories(PropertyClass propertyClass, Element el, PropertyDefinitionCategory parent) throws JDOMException { 374 PropertyDefinitionCategory cat = new DefaultPropertyDefinitionCategory(el.getAttribute("id").getIntValue(), 375 el.getAttributeValue("bundle"), 376 el.getAttributeValue("image")); 377 propertyClass.addPropertyDefinitionCategory(parent == null ? -1 : parent.getId(), cat); 378 for (Iterator i = el.getChildren().iterator(); i.hasNext();) { 379 addCategories(propertyClass, (Element) i.next(), cat); 380 } 381 } 382 383 public void activate() throws ExtensionException { 384 log.info("Activating extension descriptor " + getId()); 385 launcherType.activate(); 386 } 387 388 public void stop() throws ExtensionException { 389 log.info("Unloading extension descriptor " + getId()); 390 unloadDefinitionsCategoriesAndMessages(); 391 if (launcherType != null) { 392 launcherType.stop(); 393 } 394 } 395 396 private void unloadDefinitionsCategoriesAndMessages() { 397 for (PropertyDefinition def : propertyDefinitions.values()) { 398 def.getPropertyClass().deregisterPropertyDefinition(def.getName()); 399 } 400 for (PropertyDefinitionCategory cat : propertyDefinitionCategories.values()) { 401 cat.getPropertyClass().removePropertyDefinitionCategory(cat.getParent().getId(), cat); 402 } 403 for(String messageKey : messageKeys) { 404 messageResources.removeKey(messageKey); 405 } 406 } 407 408 public boolean canStop() { 409 try { 410 return launcherType != null ? launcherType.canStop() : true; 411 } catch (ExtensionException e) { 412 log.error("Failed to determine if extension may be stopped.", e); 413 return false; 414 } 415 } 416 417 public TunnelDescriptor getTunnel(String name) { 418 return (TunnelDescriptor) tunnels.get(name); 419 } 420 421 public ExtensionType getExtensionType() { 422 return launcherType; 423 } 424 425 private void addParameter(Element e) throws ExtensionException { 426 try { 427 ApplicationParameterDefinition definition = new ApplicationParameterDefinition(e); 428 parameters.put(definition.getName(), definition); 429 } catch (JDOMException jde) { 430 throw new ExtensionException(ExtensionException.FAILED_TO_PARSE_DESCRIPTOR, jde); 431 } 432 } 433 434 private void verifyTunnel(Element e) throws ExtensionException { 435 String name = e.getAttributeValue("name"); 436 if (name == null || name.equals("")) { 437 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 438 "name attribute required for <tunnel> element"); 439 } 440 String hostname = e.getAttributeValue("hostname"); 441 if (hostname == null || hostname.equals("")) { 442 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 443 "hostname attribute required for <tunnel> element"); 444 } 445 String port = e.getAttributeValue("port"); 446 if (port == null || port.equals("")) { 447 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, 448 "port attribute required for <tunnel> element"); 449 } 450 boolean usePreferredPort = !("false".equals(e.getAttributeValue("usePreferredPort"))); 451 tunnels.put(name, new TunnelDescriptor(name, hostname, port, usePreferredPort)); 452 453 } 454 455 public String getSmallIcon() { 456 return smallIcon; 457 } 458 459 public String getLargeIcon() { 460 return largeIcon; 461 } 462 463 public Set getParameters() { 464 return parameters.keySet(); 465 } 466 467 public Map getParametersAndDefaults() { 468 return parameters; 469 } 470 471 public ApplicationParameterDefinition getParameterDefinition(String parameter) { 472 return (ApplicationParameterDefinition) parameters.get(parameter); 473 } 474 475 public static int[] getVersion(String version) { 476 477 int idx = 0; 478 int pos = 0; 479 int[] result = new int[0]; 480 do { 481 482 idx = version.indexOf('.', pos); 483 int v; 484 if (idx > -1) { 485 v = Integer.parseInt(version.substring(pos, idx)); 486 pos = idx + 1; 487 } else { 488 int sub = version.indexOf('_', pos); 489 if (sub > -1) { 490 v = Integer.parseInt(version.substring(pos, sub)); 491 } else { 492 v = Integer.parseInt(version.substring(pos)); 493 } 494 } 495 int[] tmp = new int[result.length + 1]; 496 System.arraycopy(result, 0, tmp, 0, result.length); 497 tmp[tmp.length - 1] = v; 498 result = tmp; 499 500 } while (idx > -1); 501 502 return result; 503 } 504 505 private void verifyFiles(Element element) throws ExtensionException { 506 507 for (Iterator it = element.getChildren().iterator(); it.hasNext();) { 508 Element e = (Element) it.next(); 509 if (e.getName().equalsIgnoreCase("if")) { 510 verifyFiles(e); 511 } else if (!e.getName().equalsIgnoreCase("file")) { 512 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "Unexpected element <" + e.getName() 513 + "> found in <files>"); 514 } else { 515 processFile(e); 516 } 517 } 518 519 } 520 521 public Set getFiles() { 522 return files; 523 } 524 525 public void processFile(Element e) throws ExtensionException { 526 527 String filename = e.getAttributeValue("name"); 528 if(filename == null || filename.equals("")) { 529 filename = e.getText(); 530 } 531 532 File entry = getRealFile(filename); 533 534 if (!entry.exists()) { 535 if ("true".equals(System.getProperty("sslexplorer.useDevConfig", "false"))) { 536 log.warn("File '" + filename + "' specified in extension descriptor " + bundle.getFile().getAbsolutePath() 537 + " does not exist. As SSL-Explorer is running in Dev. mode, this will be ignored."); 538 } else { 539 throw new ExtensionException(ExtensionException.FAILED_TO_PROCESS_DESCRIPTOR, "File '" + filename 540 + "' specified in extension.xml does not exist! " + entry.getAbsolutePath()); 541 } 542 } else { 543 try { 544 e.setAttribute("checksum", String.valueOf(CoreUtil.generateChecksum(entry))); 545 } catch (IOException ioe) { 546 throw new ExtensionException(ExtensionException.INTERNAL_ERROR, ioe, "Failed to generate checksum."); 547 } 548 } 549 e.setAttribute("size", String.valueOf(entry.length())); 550 551 files.add(filename); 552 } 553 554 public boolean containsFile(String filename) { 555 return files.contains(filename); 556 } 557 558 public File getFile(String filename) throws IOException { 559 560 if (!containsFile(filename)) { 561 throw new IOException (filename + " is not a valid application file"); 562 } 563 return getRealFile(filename); 564 } 565 566 File getRealFile(String filename) { 567 if("true".equals(System.getProperty("sslexplorer.useDevConfig"))) { 568 String basedir = ".." + File.separator + bundle.getId() + File.separator + "build" + File.separator + "extension"; 569 if(filename == null) { 570 return new File (basedir); 571 } 572 File realFile = new File (basedir, filename); 573 if(realFile.exists()) { 574 return realFile; 575 } 576 } 577 String basedir = bundle.getFile().getParent(); 578 if(filename == null) { 579 return new File (basedir); 580 } 581 return new File (basedir, filename); 582 } 583 584 587 public CoreMessageResources getMessageResources() { 588 return messageResources; 589 } 590 591 public String getId() { 592 return id; 593 } 594 595 public String getName() { 596 return name; 597 } 598 599 public void setId(String id) { 600 this.id = id; 601 } 602 603 public void setName(String name) { 604 this.name = name; 605 } 606 607 public void setApplicationBundle(ExtensionBundle bundle) { 608 this.bundle = bundle; 609 } 610 611 public ExtensionBundle getApplicationBundle() { 612 return bundle; 613 } 614 615 public void removeDescriptor() { 616 } 618 619 626 public Element createProcessedDescriptorElement(SessionInfo session) { 627 Element el = (Element) element.clone(); 628 try { 629 launcherType.descriptorCreated(el, session); 630 } catch (IOException e) { 631 log.error("Failed to create processed descriptor element.", e); 632 } 633 return el; 634 } 635 636 public int compareTo(Object arg0) { 637 int c = bundle == null ? 0 : (bundle.getType() - ((ExtensionDescriptor) arg0).getApplicationBundle().getType()); 638 return c != 0 ? c : name.compareTo(((ExtensionDescriptor) arg0).name); 639 } 640 641 public class TunnelDescriptor { 642 private String name; 643 private String hostname; 644 private String port; 645 private boolean usePreferredPort; 646 647 public TunnelDescriptor(String name, String hostname, String port, boolean usePreferredPort) { 648 this.name = name; 649 this.hostname = hostname; 650 this.port = port; 651 this.usePreferredPort = usePreferredPort; 652 } 653 654 public String getName() { 655 return name; 656 } 657 658 public String getHostname() { 659 return hostname; 660 } 661 662 public String getPort() { 663 return port; 664 } 665 666 public boolean isUsePreferredPort() { 667 return usePreferredPort; 668 } 669 } 670 671 } 672 | Popular Tags |