001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.q2; 020 021 022import org.jdom2.Content; 023import org.jdom2.Element; 024import org.jdom2.Text; 025import org.jpos.core.*; 026import org.jpos.core.annotation.Config; 027import org.jpos.q2.qbean.QConfig; 028import org.jpos.util.LogSource; 029import org.jpos.util.Logger; 030import org.jpos.util.NameRegistrar; 031import org.jpos.util.Realm; 032 033import javax.management.*; 034import java.lang.reflect.Field; 035import java.lang.reflect.InvocationTargetException; 036import java.lang.reflect.Method; 037import java.net.MalformedURLException; 038import java.util.Collection; 039import java.util.List; 040import java.util.MissingResourceException; 041import java.util.ResourceBundle; 042import java.util.StringTokenizer; 043import java.util.concurrent.ExecutorService; 044import java.util.concurrent.Executors; 045 046/** 047 * Factory responsible for creating, configuring, and deploying QBean instances from XML deploy descriptors. 048 * @author <a href="mailto:taherkordy@dpi2.dpi.net.ir">Alireza Taherkordi</a> 049 * @author <a href="mailto:apr@cs.com.uy">Alejandro P. Revilla</a> 050 * @version $Revision$ $Date$ 051 */ 052@SuppressWarnings("unchecked") 053public class QFactory { 054 /** ObjectName of the QClassLoader MBean. */ 055 ObjectName loaderName; 056 /** The Q2 server instance. */ 057 Q2 q2; 058 /** Resource bundle mapping element names to class names. */ 059 ResourceBundle classMapping; 060 /** Default configuration factory used when no custom one is specified. */ 061 ConfigurationFactory defaultConfigurationFactory = new SimpleConfigurationFactory(); 062 063 /** 064 * Constructs a QFactory with the given class loader object name and Q2 instance. 065 * 066 * @param loaderName the ObjectName of the QClassLoader MBean 067 * @param q2 the Q2 server instance 068 */ 069 public QFactory (ObjectName loaderName, Q2 q2) { 070 super (); 071 this.loaderName = loaderName; 072 this.q2 = q2; 073 classMapping = ResourceBundle.getBundle(this.getClass().getName()); 074 } 075 076 /** 077 * Instantiates the MBean class described by the given XML element. 078 * 079 * @param server the Q2 server instance 080 * @param e the XML element defining the class to instantiate 081 * @return the newly instantiated object 082 * @throws ReflectionException if the class cannot be instantiated via reflection 083 * @throws MBeanException if the MBeanServer reports an error 084 * @throws InstanceNotFoundException if the class loader MBean is not found 085 */ 086 @SuppressWarnings("PMD.EmptyCatchBlock") 087 public Object instantiate (Q2 server, Element e) 088 throws ReflectionException, 089 MBeanException, 090 InstanceNotFoundException 091 { 092 String clazz = getAttributeValue (e, "class"); 093 if (clazz == null) { 094 try { 095 clazz = classMapping.getString (e.getName()); 096 } catch (MissingResourceException ex) { 097 // no class attribute, no mapping 098 // let MBeanServer do the yelling 099 } 100 } 101 MBeanServer mserver = server.getMBeanServer(); 102 if (!q2.isDisableDynamicClassloader()) 103 getExtraPath(server.getLoader(), e); 104 return mserver.instantiate (clazz, loaderName); 105 } 106 107 /** 108 * Registers and initializes a QBean MBean in the MBeanServer. 109 * 110 * @param server the Q2 server instance 111 * @param e the XML element defining the QBean 112 * @param obj the instantiated QBean object 113 * @return the registered {@link ObjectInstance} 114 * @throws MalformedObjectNameException if the object name is malformed 115 * @throws InstanceAlreadyExistsException if a bean with this name is already deployed 116 * @throws InstanceNotFoundException if the class loader MBean is not found 117 * @throws MBeanException if the MBeanServer reports an error 118 * @throws NotCompliantMBeanException if the object is not a valid MBean 119 * @throws InvalidAttributeValueException if an attribute value is invalid 120 * @throws ReflectionException if a reflective operation fails 121 * @throws ConfigurationException if configuration fails 122 */ 123 public ObjectInstance createQBean (Q2 server, Element e, Object obj) 124 throws MalformedObjectNameException, 125 InstanceAlreadyExistsException, 126 InstanceNotFoundException, 127 MBeanException, 128 NotCompliantMBeanException, 129 InvalidAttributeValueException, 130 ReflectionException, 131 ConfigurationException 132 { 133 String name = getAttributeValue (e, "name"); 134 if (name == null) 135 name = e.getName (); 136 137 ObjectName objectName = new ObjectName (Q2.QBEAN_NAME + name); 138 MBeanServer mserver = server.getMBeanServer(); 139 if(mserver.isRegistered(objectName)) { 140 throw new InstanceAlreadyExistsException (name+" has already been deployed in another file."); 141 } 142 ObjectInstance instance = mserver.registerMBean ( 143 obj, objectName 144 ); 145 try { 146 setAttribute (mserver, objectName, "Name", name); 147 String logger = getAttributeValue (e, "logger"); 148 if (logger != null) 149 setAttribute (mserver, objectName, "Logger", logger); 150 String realm = getAttributeValue (e, "realm"); 151 if (realm != null) 152 setAttribute (mserver, objectName, "Realm", realm); 153 setAttribute (mserver, objectName, "Server", server); 154 setAttribute (mserver, objectName, "Persist", e); 155 configureQBean(mserver,objectName,e); 156 setConfiguration (obj, e); // handle legacy (QSP v1) Configurables 157 158 if (obj instanceof QBean) 159 mserver.invoke (objectName, "init", null, null); 160 } 161 catch (Throwable t) { 162 mserver.unregisterMBean(objectName); 163 t.fillInStackTrace(); 164 throw t; 165 } 166 167 return instance; 168 } 169 /** 170 * Returns the Q2 server instance associated with this factory. 171 * 172 * @return the Q2 server instance 173 */ 174 public Q2 getQ2() { 175 return q2; 176 } 177 178 private void getExtraPath (QClassLoader loader, Element e) { 179 Element classpathElement = e.getChild ("classpath"); 180 if (classpathElement != null) { 181 try { 182 loader = loader.scan (true); // force a new classloader 183 } catch (Throwable t) { 184 getQ2().getLog().error(t); 185 } 186 for (Element u : classpathElement.getChildren("url")) { 187 try { 188 loader.addURL(u.getTextTrim()); 189 } catch (MalformedURLException ex) { 190 q2.getLog().warn(u.getTextTrim(), ex); 191 } 192 } 193 } 194 } 195 196 /** 197 * Sets an attribute on the given MBean, silently ignoring unknown attributes. 198 * 199 * @param server the MBeanServer 200 * @param objectName the ObjectName of the MBean 201 * @param attribute the attribute name 202 * @param value the attribute value 203 * @throws InstanceNotFoundException if the MBean is not found 204 * @throws MBeanException if the MBeanServer reports an error 205 * @throws InvalidAttributeValueException if the value is invalid 206 * @throws ReflectionException if a reflective operation fails 207 */ 208 @SuppressWarnings("PMD.EmptyCatchBlock") 209 public void setAttribute 210 (MBeanServer server, ObjectName objectName, 211 String attribute, Object value) 212 throws InstanceNotFoundException, 213 MBeanException, 214 InvalidAttributeValueException, 215 ReflectionException 216 { 217 try { 218 server.setAttribute ( 219 objectName, new Attribute (attribute, value) 220 ); 221 } catch (AttributeNotFoundException ex) { 222 // okay to fail 223 } catch (InvalidAttributeValueException ex) { 224 // okay to fail (produced by some application servers instead of AttributeNotFoundException) 225 } 226 } 227 228 /** 229 * Invokes the {@code start} method on the given QBean MBean. 230 * 231 * @param server the Q2 server instance 232 * @param objectName the ObjectName of the QBean MBean 233 * @throws InstanceNotFoundException if the MBean is not found 234 * @throws MBeanException if the MBeanServer reports an error 235 * @throws ReflectionException if a reflective operation fails 236 */ 237 public void startQBean (Q2 server, ObjectName objectName) 238 throws InstanceNotFoundException, 239 MBeanException, 240 ReflectionException 241 { 242 MBeanServer mserver = server.getMBeanServer(); 243 mserver.invoke (objectName, "start", null, null); 244 } 245 246 /** 247 * Stops, destroys, and unregisters the given QBean MBean. 248 * 249 * @param server the Q2 server instance 250 * @param objectName the ObjectName of the QBean MBean 251 * @param obj the underlying QBean object 252 * @throws InstanceNotFoundException if the MBean is not found 253 * @throws MBeanException if the MBeanServer reports an error 254 * @throws ReflectionException if a reflective operation fails 255 */ 256 public void destroyQBean (Q2 server, ObjectName objectName, Object obj) 257 throws InstanceNotFoundException, 258 MBeanException, 259 ReflectionException 260 { 261 MBeanServer mserver = server.getMBeanServer(); 262 if (obj instanceof QBean) { 263 mserver.invoke (objectName, "stop", null, null); 264 mserver.invoke (objectName, "destroy", null, null); 265 } 266 if (objectName != null) 267 mserver.unregisterMBean (objectName); 268 } 269 270 /** 271 * Configures a QBean by applying attributes from the given XML element. 272 * 273 * @param server the MBeanServer 274 * @param objectName the ObjectName of the QBean 275 * @param e the XML element containing {@code attr} child elements 276 * @throws ConfigurationException if attribute setting fails 277 */ 278 public void configureQBean(MBeanServer server, ObjectName objectName, Element e) 279 throws ConfigurationException 280 { 281 try { 282 AttributeList attributeList = getAttributeList(e); 283 for (Object anAttributeList : attributeList) 284 server.setAttribute(objectName, (Attribute) anAttributeList); 285 } catch (Exception e1) { 286 throw new ConfigurationException(e1); 287 } 288 289 } 290 /** 291 * Builds a JMX {@link AttributeList} from {@code attr} child elements of the given element. 292 * 293 * @param e the XML element containing {@code attr} child elements 294 * @return list of JMX Attribute objects 295 * @throws ConfigurationException if attribute conversion fails 296 */ 297 public AttributeList getAttributeList(Element e) 298 throws ConfigurationException 299 { 300 AttributeList attributeList = new AttributeList(); 301 List childs = e.getChildren("attr"); 302 for (Object child : childs) { 303 Element childElement = (Element) child; 304 String name = childElement.getAttributeValue("name"); 305 name = getAttributeName(name); 306 Attribute attr = new Attribute(name, getObject(childElement)); 307 attributeList.add(attr); 308 } 309 return attributeList; 310 } 311 312 /** 313 * Creates an object from a definition element. 314 * The element may have an attribute called type indicating the type of the object 315 * to create, if this attribute is not present java.lang.String is assumed. 316 * int, long and boolean are converted to their wrappers. 317 * @return The created object. 318 * @param childElement Dom Element with the definition of the object. 319 * @throws ConfigurationException If an exception is found trying to create the object. 320 */ 321 @SuppressWarnings("unchecked") 322 protected Object getObject(Element childElement) 323 throws ConfigurationException 324 { 325 String type = childElement.getAttributeValue("type","java.lang.String"); 326 if ("int".equals (type)) 327 type = "java.lang.Integer"; 328 else if ("long".equals (type)) 329 type = "java.lang.Long"; 330 else if ("boolean".equals (type)) 331 type = "java.lang.Boolean"; 332 333 String value = childElement.getText(); 334 value = Environment.getEnvironment().getProperty(value, value); 335 try { 336 Class<?> attributeType = Class.forName(type); 337 if(Collection.class.isAssignableFrom(attributeType)) 338 return getCollection(attributeType, childElement); 339 else{ 340 Class[] parameterTypes = {"".getClass()}; 341 Object[] parameterValues = {value}; 342 return attributeType.getConstructor(parameterTypes).newInstance(parameterValues); 343 } 344 } catch (Exception e1) { 345 throw new ConfigurationException(e1); 346 } 347 348 } 349 350 351 /** Creates a collection from a definition element with the format. 352 * <pre>{@code 353 * <attr type="..."> 354 * <item type="...">...</item> 355 * ... 356 * </attr> 357 * }</pre> 358 * @param type class type 359 * @param e the Element 360 * @throws ConfigurationException if configuration is invalid 361 * @return the object collection 362 */ 363 @SuppressWarnings("unchecked") 364 protected Collection getCollection(Class type, Element e) 365 throws ConfigurationException 366 { 367 try{ 368 Collection<Object> col = (Collection<Object>) type.newInstance(); 369 for (Element o : e.getChildren("item")) { 370 col.add(getObject(o)); 371 } 372 return col; 373 }catch(Exception e1){ 374 throw new ConfigurationException(e1); 375 } 376 } 377 /** 378 * Capitalizes the first letter of an attribute name to produce a setter/getter name. 379 * 380 * @param name the raw attribute name 381 * @return the capitalized attribute name 382 */ 383 public String getAttributeName(String name) 384 { 385 if (name == null) 386 throw new NullPointerException("attribute name can not be null"); 387 StringBuilder tmp = new StringBuilder(name); 388 if (tmp.length() > 0) 389 tmp.setCharAt(0,name.toUpperCase().charAt(0)) ; 390 return tmp.toString(); 391 } 392 393 /** 394 * Instantiates the class with the given name via the Q2 MBeanServer. 395 * 396 * @param <T> the expected return type 397 * @param clazz the fully-qualified class name to instantiate 398 * @return the newly instantiated object 399 * @throws ConfigurationException if instantiation fails 400 */ 401 public <T> T newInstance (String clazz) 402 throws ConfigurationException 403 { 404 try { 405 MBeanServer mserver = q2.getMBeanServer(); 406 return (T)mserver.instantiate (clazz, loaderName); 407 } catch (Exception e) { 408 throw new ConfigurationException (clazz, e); 409 } 410 } 411 412 /** 413 * Instantiates the given class via the Q2 MBeanServer. 414 * 415 * @param <T> the expected return type 416 * @param clazz the Class to instantiate 417 * @return the newly instantiated object 418 * @throws ConfigurationException if instantiation fails 419 */ 420 public <T> T newInstance (Class<T> clazz) 421 throws ConfigurationException 422 { 423 return newInstance(clazz.getName()); 424 } 425 426 /** 427 * Creates a new instance from the values in {@code Element e}.<br/> 428 * <p> 429 * The method honors the {@code enabled} attribute in the given {@code Element e}, 430 * returning null immediately if {@code enabled} is computed to a true-equivalent.</p> 431 * <p> 432 * It also calls {@link #setLogger} to set logger and realm, and {@link #setConfiguration(Object, Element)} 433 * to trigger the standard [auto]configuration sequence from properties and XML.</p> 434 * 435 * @param <T> expected concrete type returned to the caller 436 * @param e The XML config 437 * @return the new instance, or null if not enabled 438 * @throws ConfigurationException if the instance can't be created (e.g. class not found) 439 * or the configuration process itself threw the exception. 440 */ 441 public <T> T newInstance(Element e) throws ConfigurationException { 442 if (!QFactory.isEnabled(e)) 443 return null; 444 T obj = newInstance(QFactory.getAttributeValue (e, "class")); 445 setLogger (obj, e); 446 setConfiguration(obj, e); 447 return obj; 448 } 449 450 /** 451 * Returns a {@link Configuration} built from the given XML element, with optional merge. 452 * 453 * @param e the XML element to build the configuration from 454 * @return the resulting Configuration 455 * @throws ConfigurationException if configuration building or merging fails 456 */ 457 public Configuration getConfiguration (Element e) 458 throws ConfigurationException 459 { 460 String configurationFactoryClazz = getAttributeValue(e, "configuration-factory"); 461 ConfigurationFactory cf = configurationFactoryClazz != null ? 462 (ConfigurationFactory) newInstance(configurationFactoryClazz) : defaultConfigurationFactory; 463 464 Configuration cfg = cf.getConfiguration(e); 465 String merge = getAttributeValue(e, "merge-configuration"); 466 if (merge != null) { 467 StringTokenizer st = new StringTokenizer(merge, ", "); 468 while (st.hasMoreElements()) { 469 try { 470 Configuration c = QConfig.getConfiguration(st.nextToken()); 471 for (String k : c.keySet()) { 472 if (cfg.get(k, null) == null) { 473 String[] v = c.getAll(k); 474 switch (v.length) { 475 case 0: 476 break; 477 case 1: 478 cfg.put(k, v[0]); 479 break; 480 default: 481 cfg.put(k, v); 482 } 483 } 484 } 485 } catch (NameRegistrar.NotFoundException ex) { 486 throw new ConfigurationException (ex.getMessage()); 487 } 488 } 489 } 490 return cfg; 491 } 492 493 /** 494 * Sets the logger and realm on the object if it implements {@link LogSource}. 495 * 496 * @param obj the object to configure 497 * @param e the XML element containing logger/realm attributes 498 */ 499 public void setLogger (Object obj, Element e) { 500 setLogger(obj, e, null); 501 } 502 503 /** 504 * Sets the logger and realm on the object if it implements {@link LogSource}, with a fallback realm. 505 * 506 * @param obj the object to configure 507 * @param e the XML element containing logger/realm attributes 508 * @param fallbackRealm realm to use when none is specified in the element 509 */ 510 public void setLogger (Object obj, Element e, String fallbackRealm) { 511 if (obj instanceof LogSource) { 512 String loggerName = getAttributeValue (e, "logger"); 513 if (loggerName != null) { 514 String realm = getAttributeValue (e, "realm"); 515 if (realm == null) 516 realm = fallbackRealm != null ? fallbackRealm : defaultRealm(e.getName()); 517 Logger logger = Logger.getLogger (loggerName); 518 ((LogSource)obj).setLogger (logger, realm); 519 } 520 } 521 } 522 523 /** 524 * Returns the default realm name for the given XML element name. 525 * 526 * @param elementName the XML element name (e.g. "channel", "server") 527 * @return the corresponding default realm string 528 */ 529 protected String defaultRealm(String elementName) { 530 return switch (elementName) { 531 case "channel" -> Realm.COMM_CHANNEL; 532 case "server" -> Realm.COMM_SERVER; 533 case "mux" -> Realm.COMM_MUX; 534 case "client" -> Realm.COMM_CLIENT; 535 default -> elementName; 536 }; 537 } 538 539 /** 540 * Returns the value of the named attribute from the element, with environment variable substitution. 541 * 542 * @param e the XML element 543 * @param name the attribute name 544 * @return the resolved attribute value, or {@code null} if not present 545 */ 546 public static String getAttributeValue (Element e, String name) { 547 String s = e.getAttributeValue(name); 548 return Environment.getEnvironment().getProperty(s, s); 549 } 550 551 /** 552 * Applies configuration from the XML element to the given object. 553 * 554 * @param obj the object to configure 555 * @param e the XML element containing configuration attributes 556 * @throws ConfigurationException if configuration fails 557 */ 558 public void setConfiguration (Object obj, Element e) 559 throws ConfigurationException 560 { 561 try { 562 Configuration cfg = getConfiguration (e); 563 autoconfigure(obj, cfg); 564 565 if (obj instanceof Configurable) 566 ((Configurable)obj).setConfiguration (cfg); 567 if (obj instanceof XmlConfigurable) 568 ((XmlConfigurable)obj).setConfiguration(e); 569 } catch (ConfigurationException | IllegalAccessException ex) { 570 throw new ConfigurationException (ex); 571 } 572 } 573 /** 574 * Try to invoke a method (usually a setter) on the given object 575 * silently ignoring if method does not exist 576 * @param obj the object 577 * @param m method to invoke 578 * @param p parameter 579 * @throws ConfigurationException if method happens to throw an exception 580 */ 581 public static void invoke (Object obj, String m, Object p) 582 throws ConfigurationException 583 { 584 invoke (obj, m, p, p != null ? p.getClass() : null); 585 } 586 587 /** 588 * Try to invoke a method (usually a setter) on the given object 589 * silently ignoring if method does not exist 590 * @param obj the object 591 * @param m method to invoke 592 * @param p parameter 593 * @param pc parameter class 594 * @throws ConfigurationException if method happens to throw an exception 595 */ 596 @SuppressWarnings("PMD.EmptyCatchBlock") 597 public static void invoke (Object obj, String m, Object p, Class pc) 598 throws ConfigurationException 599 { 600 try { 601 if (p!=null) { 602 Class[] paramTemplate = { pc }; 603 Method method = obj.getClass().getMethod(m, paramTemplate); 604 Object[] param = new Object[1]; 605 param[0] = p; 606 method.invoke (obj, param); 607 } else { 608 Method method = obj.getClass().getMethod(m); 609 method.invoke (obj); 610 } 611 } catch (NoSuchMethodException ignored) { 612 } catch (NullPointerException ignored) { 613 } catch (IllegalAccessException ignored) { 614 } catch (InvocationTargetException e) { 615 throw new ConfigurationException ( 616 obj.getClass().getName() + "." + m + "(" + p +")" , 617 e.getTargetException() 618 ); 619 } 620 } 621 /** 622 * Returns {@code true} if the element's {@code enabled} attribute evaluates to true. 623 * 624 * @param e the XML element 625 * @return {@code true} if enabled 626 */ 627 public static boolean isEnabled(Element e) { 628 return isTrue(getEnabledAttribute(e)); 629 } 630 631 /** 632 * Returns {@code true} if the element's {@code eager-start} attribute evaluates to true. 633 * 634 * @param e the XML element 635 * @return {@code true} if eager start is requested 636 */ 637 public static boolean isEagerStart(Element e) { 638 return isTrue(getEagerStartAttribute(e)); 639 } 640 private static boolean isTrue(String attribute) { 641 return "true".equalsIgnoreCase(attribute) || 642 "yes".equalsIgnoreCase(attribute) || 643 attribute.contains(Environment.getEnvironment().getName()); 644 } 645 646 /** 647 * Returns the value of the {@code enabled} attribute, defaulting to {@code "true"}. 648 * 649 * @param e the XML element 650 * @return the enabled attribute value 651 */ 652 public static String getEnabledAttribute (Element e) { 653 return getAttribute(e, "enabled", "true"); 654 } 655 656 /** 657 * Returns the value of the {@code eager-start} attribute, defaulting to {@code "false"}. 658 * 659 * @param e the XML element 660 * @return the eager-start attribute value 661 */ 662 public static String getEagerStartAttribute (Element e) { 663 return getAttribute(e, "eager-start", "false"); 664 } 665 private static String getAttribute (Element e, String attr, String def) { 666 return Environment.get(e.getAttributeValue(attr, def)); 667 } 668 669 /** 670 * Automatically configures fields annotated with {@link Config} on the given object. 671 * 672 * @param obj the object to autoconfigure 673 * @param cfg the Configuration to read values from 674 * @throws IllegalAccessException if field access fails 675 */ 676 @SuppressWarnings("rawtypes") 677 public static void autoconfigure (Object obj, Configuration cfg) throws IllegalAccessException { 678 Class cc = obj.getClass(); 679 do { 680 Field[] fields = cc.getDeclaredFields(); 681 for (Field field : fields) { 682 if (field.isAnnotationPresent(Config.class)) { 683 Config config = field.getAnnotation(Config.class); 684 String v = cfg.get(config.value(), null); 685 if (v != null) { 686 if (!field.isAccessible()) 687 field.setAccessible(true); 688 Class<?> c = field.getType(); 689 if (c.isAssignableFrom(String.class)) 690 field.set(obj, v); 691 else if (c.isAssignableFrom(int.class) || c.isAssignableFrom(Integer.class)) 692 field.set(obj, cfg.getInt(config.value())); 693 else if (c.isAssignableFrom(long.class) || c.isAssignableFrom(Long.class)) 694 field.set(obj, cfg.getLong(config.value())); 695 else if (c.isAssignableFrom(double.class) || c.isAssignableFrom(Double.class)) 696 field.set(obj, cfg.getDouble(config.value())); 697 else if (c.isAssignableFrom(boolean.class) || c.isAssignableFrom(Boolean.class)) 698 field.set(obj, cfg.getBoolean(config.value())); 699 else if (c.isEnum()) 700 field.set(obj, Enum.valueOf((Class<Enum>) c, v)); 701 else if (c.isArray()) { 702 Class<?> ct = c.getComponentType(); 703 if (ct.isAssignableFrom(String.class)) 704 field.set(obj, cfg.getAll(config.value())); 705 else if (ct.isAssignableFrom(int.class) || ct.isAssignableFrom(Integer.class)) 706 field.set(obj, cfg.getInts(config.value())); 707 else if (ct.isAssignableFrom(long.class) || ct.isAssignableFrom(Long.class)) 708 field.set(obj, cfg.getLongs(config.value())); 709 else if (ct.isAssignableFrom(double.class) || ct.isAssignableFrom(Double.class)) 710 field.set(obj, cfg.getDoubles(config.value())); 711 } 712 } 713 } 714 } 715 cc = cc.getSuperclass(); 716 } while (!cc.equals(Object.class)); 717 } 718 719 /** 720 * Decorates an {@link Element} by replacing its attributes, and content {@link Environment} properties references. 721 * @param e The element being decorated. 722 * @return The modified element, it is modified in place, but it is returned to ease method chaining or call composition. 723 */ 724 public static Element expandEnvProperties(Element e) { 725 expandEnvProperties(e, Environment.getEnvironment()); 726 return e; 727 } 728 729 /** 730 * Creates an {@link ExecutorService} using either virtual or platform threads. 731 * 732 * @param virtual {@code true} to use virtual threads, {@code false} for platform threads 733 * @return a new ExecutorService 734 */ 735 public static ExecutorService executorService(boolean virtual) { 736 return virtual ? 737 Executors.newVirtualThreadPerTaskExecutor() : 738 Executors.newThreadPerTaskExecutor( 739 Thread.ofPlatform().inheritInheritableThreadLocals(true) 740 .factory() 741 ); 742 } 743 744 /** 745 * Recursively replaces {@link Environment} properties in an element's attributes, its content and its children. 746 * Properties are replaced in place. 747 * @param e The element in which properties are being replaced. 748 * @param env The {@link Environment}'s singleton instance. 749 */ 750 private static void expandEnvProperties(Element e, Environment env) { 751 if (Boolean.parseBoolean(e.getAttributeValue("verbatim"))) return; 752 for (org.jdom2.Attribute attr : e.getAttributes()) { 753 String value = attr.getValue(); 754 attr.setValue(env.getProperty(value, value)); 755 } 756 for (Content child : e.getContent()) { 757 if (child instanceof Element) { 758 expandEnvProperties((Element) child, env); 759 } else if (child instanceof Text text) { 760 String textValue = text.getText(); 761 text.setText(env.getProperty(textValue, textValue)); 762 } 763 } 764 } 765 766}