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 021import org.jdom2.Element; 022import org.jpos.core.Configurable; 023import org.jpos.core.Configuration; 024import org.jpos.core.ConfigurationException; 025import org.jpos.util.*; 026 027import java.beans.BeanInfo; 028import java.beans.Introspector; 029import java.beans.PropertyDescriptor; 030import java.io.ByteArrayOutputStream; 031import java.io.Closeable; 032import java.io.PrintStream; 033import java.lang.reflect.Method; 034import java.net.URL; 035import java.util.Iterator; 036import java.util.concurrent.ScheduledThreadPoolExecutor; 037 038/** 039 * Base class providing lifecycle management and configuration support for Q2 beans (QBean components). 040 * @author <a href="mailto:taherkordy@dpi2.dpi.net.ir">Alireza Taherkordi</a> 041 * @author <a href="mailto:apr@cs.com.uy">Alejandro P. Revilla</a> 042 */ 043public class QBeanSupport 044 implements QBean, QPersist, QBeanSupportMBean, Configurable 045{ 046 Element persist; 047 int state; 048 Q2 server; 049 final Object modifyLock = new Object(); 050 boolean modified; 051 String name; 052 String configuredRealm; 053 /** Logger associated with this bean. */ 054 protected Log log; 055 /** Configuration applied to this bean by the container. */ 056 protected Configuration cfg; 057 /** Lazily-allocated scheduler shared with subclasses; see {@link #getScheduledThreadPoolExecutor()}. */ 058 protected ScheduledThreadPoolExecutor scheduledThreadPoolExecutor; 059 060 /** Default constructor. */ 061 public QBeanSupport () { 062 super(); 063 setLogger (Q2.LOGGER_NAME); 064 state = -1; 065 } 066 067 @Override 068 public void setServer (Q2 server) { 069 this.server = server; 070 } 071 072 @Override 073 public Q2 getServer () { 074 return server; 075 } 076 /** 077 * Returns the {@link QFactory} associated with the hosting Q2 server. 078 * 079 * @return the server's QFactory 080 */ 081 public QFactory getFactory () { 082 return getServer().getFactory (); 083 } 084 085 @Override 086 public void setName (String name) { 087 if (this.name == null) 088 this.name = name; 089 syncLogConfiguration(); 090 setModified (true); 091 } 092 093 @Override 094 public void setLogger (String loggerName) { 095 log = Log.getLog (loggerName, resolveRealm()); 096 syncLogConfiguration(); 097 setModified (true); 098 } 099 100 @Override 101 public void setRealm (String realm) { 102 configuredRealm = realm; 103 syncLogConfiguration(); 104 } 105 106 @Override 107 public String getRealm() { 108 return log != null ? log.getRealm() : null; 109 } 110 111 @Override 112 public String getLogger () { 113 return log != null ? log.getLogger().getName() : null; 114 } 115 116 /** 117 * Returns the {@link Log} used by this bean. 118 * 119 * @return the configured log instance, or {@code null} if logging has not been initialized 120 */ 121 public Log getLog () { 122 return log; 123 } 124 125 /** 126 * Returns the default realm for this bean; subclasses may override. 127 * @return default realm string, or null 128 */ 129 protected String defaultRealm() { 130 return null; 131 } 132 133 @Override 134 public String getName () { 135 return name; 136 } 137 138 @Override 139 public void init () { 140 if (state == -1) { 141 setModified (false); 142 try { 143 initService(); 144 state = QBean.STOPPED; 145 } catch (Throwable t) { 146 log.warn ("init", t); 147 } 148 } 149 } 150 151 @Override 152 public synchronized void start() { 153 if (state != QBean.DESTROYED && 154 state != QBean.STOPPED && 155 state != QBean.FAILED) 156 return; 157 158 this.state = QBean.STARTING; 159 160 try { 161 startService(); 162 } catch (Throwable t) { 163 state = QBean.FAILED; 164 log.warn ("start", t); 165 return; 166 } 167 state = QBean.STARTED; 168 } 169 170 @Override 171 public synchronized void stop () { 172 if (state != QBean.STARTED) 173 return; 174 state = QBean.STOPPING; 175 try { 176 stopService(); 177 } catch (Throwable t) { 178 state = QBean.FAILED; 179 log.warn ("stop", t); 180 return; 181 } 182 state = QBean.STOPPED; 183 } 184 185 @Override 186 public void destroy () { 187 if (state == QBean.DESTROYED) 188 return; 189 if (state != QBean.STOPPED) 190 stop(); 191 192 if (scheduledThreadPoolExecutor != null) { 193 scheduledThreadPoolExecutor.shutdown(); 194 scheduledThreadPoolExecutor = null; 195 } 196 try { 197 destroyService(); 198 } 199 catch (Throwable t) { 200 log.warn ("destroy", t); 201 } 202 state = QBean.DESTROYED; 203 } 204 205 @Override 206 public int getState () { 207 return state; 208 } 209 210 @Override 211 public URL[] getLoaderURLS() { 212 return server.getLoader().getURLs(); 213 } 214 215 @Override 216 public QClassLoader getLoader() { 217 return server.getLoader(); 218 } 219 220 @Override 221 public String getStateAsString () { 222 return state >= 0 ? stateString[state] : "Unknown"; 223 } 224 225 /** 226 * Sets the lifecycle state of this bean. 227 * 228 * @param state one of the QBean state constants 229 */ 230 public void setState (int state) { 231 this.state = state; 232 } 233 234 @Override 235 public void setPersist (Element persist) { 236 this.persist = persist ; 237 } 238 239 @Override 240 public Element getPersist () { 241 setModified (false); 242 return persist; 243 } 244 245 /** 246 * Marks this bean's persistent state as modified or in sync. 247 * 248 * @param modified {@code true} if the bean has unsaved changes 249 */ 250 public void setModified (boolean modified) { 251 synchronized (this.modifyLock) { 252 this.modified = modified; 253 } 254 } 255 256 @Override 257 public boolean isModified () { 258 synchronized (this.modifyLock) { 259 return modified; 260 } 261 } 262 263 /** 264 * Indicates whether this bean is currently starting or running. 265 * 266 * @return {@code true} if the state is {@link QBean#STARTING} or {@link QBean#STARTED} 267 */ 268 public boolean running () { 269 return state == QBean.STARTING || state == QBean.STARTED; 270 } 271 272 @Override 273 public void setConfiguration (Configuration cfg) 274 throws ConfigurationException 275 { 276 this.cfg = cfg; 277 } 278 /** 279 * Returns the configuration applied to this bean. 280 * 281 * @return the bean's {@link Configuration}, or {@code null} if not yet configured 282 */ 283 public Configuration getConfiguration () { 284 return cfg; 285 } 286 287 private String resolveRealm() { 288 if (configuredRealm != null) 289 return configuredRealm; 290 String defaultRealm = defaultRealm(); 291 if (defaultRealm != null) 292 return defaultRealm; 293 return name != null ? name : getClass().getName(); 294 } 295 296 private void syncLogConfiguration() { 297 if (log == null) 298 return; 299 log.setRealm(resolveRealm()); 300 log.removeDefaultTag("component"); 301 if (configuredRealm == null && defaultRealm() != null && name != null) 302 log.setDefaultTag("component", name); 303 } 304 305 /** 306 * Returns a textual dump of this bean's state. 307 * 308 * <p>If the bean implements {@link Loggeable}, its {@code dump} output is returned; 309 * otherwise this delegates to {@link #toString()}. 310 * 311 * @return a string representation suitable for diagnostics 312 */ 313 public String getDump () { 314 if (this instanceof Loggeable) { 315 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 316 PrintStream p = new PrintStream(baos); 317 ((Loggeable)this).dump(p, ""); 318 return baos.toString(); 319 } 320 return toString(); 321 } 322 /** 323 * Called during the init lifecycle phase; subclasses may override. 324 * @throws Exception on error 325 */ 326 protected void initService() throws Exception {} 327 /** 328 * Called during the start lifecycle phase; subclasses may override. 329 * @throws Exception on error 330 */ 331 protected void startService() throws Exception {} 332 /** 333 * Called during the stop lifecycle phase; subclasses may override. 334 * @throws Exception on error 335 */ 336 protected void stopService() throws Exception {} 337 /** 338 * Called during the destroy lifecycle phase; subclasses may override. 339 * @throws Exception on error 340 */ 341 protected void destroyService() throws Exception {} 342 343 /** 344 * Lazily creates and returns a {@link ScheduledThreadPoolExecutor} for use by subclasses. 345 * 346 * @return shared scheduled executor for this bean 347 */ 348 protected synchronized ScheduledThreadPoolExecutor getScheduledThreadPoolExecutor() { 349 if (scheduledThreadPoolExecutor == null) 350 scheduledThreadPoolExecutor = ConcurrentUtil.newScheduledThreadPoolExecutor(); 351 return scheduledThreadPoolExecutor; 352 } 353 354 /** 355 * Builds a JDOM {@link Element} reflecting the bean's persistent attributes, 356 * driven by the JavaBean introspection of {@code mbeanClass}. 357 * 358 * @param name the root element name 359 * @param mbeanClass the MBean interface whose properties drive serialization 360 * @return populated element ready for persistence 361 */ 362 protected Element createElement (String name, Class mbeanClass) { 363 Element e = new Element (name); 364 Element classPath = persist != null ? 365 persist.getChild ("classpath") : null; 366 if (classPath != null) 367 e.addContent (classPath); 368 e.setAttribute ("class", getClass().getName()); 369 if (!e.getName().equals (getName ())) 370 e.setAttribute ("name", getName()); 371 String loggerName = getLogger(); 372 if (loggerName != null) 373 e.setAttribute ("logger", loggerName); 374 375 try { 376 BeanInfo info = Introspector.getBeanInfo (mbeanClass); 377 PropertyDescriptor[] desc = info.getPropertyDescriptors(); 378 for (PropertyDescriptor aDesc : desc) { 379 if (aDesc.getWriteMethod() != null) { 380 Method read = aDesc.getReadMethod(); 381 Object obj = read.invoke(this); 382 String type = read.getReturnType().getName(); 383 if ("java.lang.String".equals(type)) 384 type = null; 385 386 addAttr(e, aDesc.getName(), obj, type); 387 } 388 } 389 } catch (Exception ex) { 390 log.warn ("get-persist", ex); 391 } 392 return e; 393 } 394 /** 395 * Appends an {@code <attr>} child element with name/type/value to {@code e}. 396 * 397 * @param e parent element 398 * @param name attribute name 399 * @param obj value (rendered via {@code toString()}; {@code null} becomes the literal "null") 400 * @param type optional Java type hint, or {@code null} to omit 401 */ 402 protected void addAttr (Element e, String name, Object obj, String type) { 403 String value = obj == null ? "null" : obj.toString(); 404 Element attr = new Element ("attr"); 405 attr.setAttribute ("name", name); 406 if (type != null) 407 attr.setAttribute ("type", type); 408 attr.setText (value); 409 e.addContent (attr); 410 } 411 /** 412 * Iterates over top-level {@code <attr>} children of the persisted element. 413 * 414 * @return iterator over the persisted attribute elements 415 */ 416 protected Iterator getAttrs () { 417 return getPersist().getChildren ("attr").iterator(); 418 } 419 /** 420 * Iterates over {@code <attr>} children nested under the given parent element name. 421 * 422 * @param parent name of the parent element under {@link #getPersist()} 423 * @return iterator over the nested attribute elements 424 */ 425 protected Iterator getAttrs (String parent) { 426 return getPersist().getChild(parent). 427 getChildren("attr").iterator(); 428 } 429 /** 430 * Updates the value of the named attribute within an iterator of {@code <attr>} elements. 431 * 432 * @param attrs iterator over candidate attribute elements 433 * @param name name of the attribute to update 434 * @param obj new value (rendered via {@code toString()}; {@code null} becomes "null") 435 */ 436 protected void setAttr (Iterator attrs, String name, Object obj) { 437 String value = obj == null ? "null" : obj.toString (); 438 while (attrs.hasNext ()) { 439 Element e = (Element) attrs.next (); 440 if (name.equals (e.getAttributeValue ("name"))) { 441 e.setText(value); 442 break; 443 } 444 } 445 } 446 /** 447 * Iterates over {@code <property>} children nested under the given parent element name. 448 * 449 * @param parent name of the parent element under {@link #getPersist()} 450 * @return iterator over the nested property elements 451 */ 452 protected Iterator getProperties (String parent) { 453 return getPersist().getChild (parent). 454 getChildren ("property").iterator(); 455 } 456 /** 457 * Updates the value attribute of the named property within an iterator of {@code <property>} elements. 458 * 459 * @param props iterator over candidate property elements 460 * @param name name of the property to update 461 * @param value new value to assign 462 */ 463 protected void setProperty (Iterator props, String name, String value) { 464 while (props.hasNext()) { 465 Element e = (Element) props.next(); 466 if (name.equals (e.getAttributeValue ("name"))) { 467 e.setAttribute ("value", value); 468 break; 469 } 470 } 471 } 472 /** 473 * Returns the value of the named property from an iterator of {@code <property>} elements. 474 * 475 * @param props iterator over candidate property elements 476 * @param name name of the property to look up 477 * @return the property's value, or {@code null} if not found 478 */ 479 protected String getProperty (Iterator props, String name) { 480 while (props.hasNext()) { 481 Element e = (Element) props.next(); 482 if (name.equals (e.getAttributeValue ("name"))) { 483 return e.getAttributeValue ("value"); 484 } 485 } 486 return null; 487 } 488 /** 489 * Closes a sequence of {@link Closeable} resources, logging any failures as a warning. 490 * 491 * @param closeables resources to close (each is closed independently of the others) 492 */ 493 protected void close (Closeable... closeables) { 494 LogEvent evt = null; 495 for (Closeable c : closeables) { 496 try { 497 c.close(); 498 } catch (Exception e) { 499 if (evt == null) 500 evt = getLog().createWarn(); 501 evt.addMessage(e); 502 } 503 } 504 if (evt != null) 505 Logger.log(evt); 506 } 507}