2009-12-22

Fluchomat

Neulich hörte ich aus der Ecke unseres Testers so merkwürdige Flüche ...
Ja, ja, so ein Fluchomat ist schon eine tolle Sache.
Tip: Koordinate [4,3] ist mein Favorit

Nachtrag: Zum Schluß hörte ich dann aber doch noch den unten rechts ;)

2009-11-22

Lüsterleise PCs

Wenn der PC leise flüsternd
Unter dem Schreibtisch hockt
Der Spieler durch sein Fleise lüsternd
Die Nacht durchzockt

Wenn der Spieler lüsternd leiser
Unter den Schreibtisch kriecht
Der PC vom Flüstern heiser
Die Nacht im Standby siecht

Dann nennt man das Lüster Leise
Oder heise?


Schöner Freud'scher Verschreiber (gesehen am 2009-11-22 auf www.heise.de).

2009-10-22

Ausdruck ohne Wert

Das folgende ist meine Messagebox des Monats:

Woher Access wohl weiß, daß meine Eingabe einfach wertlos ist?
Ob das ein Versuch ist, Datenmüll vorzubeugen?
Darf ich keine Ausdrücke wie Affenarbeit, Hohldreher oder Plinse eingeben?

2009-06-16

EJB-Konfiguration auf glassfish manipulieren ohne Reboot mittels MBeans

Hintergrund: Nachdem ein EJB (Web-)Service auf dem Application Server (glassfish) deployed wurde, soll die Konfiguration geändert werden ohne den Service neu zu deployen oder gar den Server neu starten zu müssen.

Daher scheidet ein config.xml im jar-File des Service aus. Ebenso wie die irreführend als dynamisch benannten Server-Properties (hier ist vor dem Wirksamwerden immer ein kompletter Serverneustart nötig)

Lösung: Custom MBeans

Und so kann es gehen (schöner geht immer):
* Ein Interface ConfigMBean
* Eine abstrakte Klasse Config
* Zwei Implementationen Development und Production
* Einen ConfigReader (der nur mit Config hantiert)
* Ein EJB namens DemoBusiness (der via ConfigReader auf ein MBean zugreift)

Als Schmankerl cached der ConfigReader und alle ConfigMBean Implementierungen [Development und Production] besitzen eine hardverdrahtete DefaultConfig. Somit funktioniert dieser auch, wenn gerade kein MBean registriert/deployed ist.

Der ConfigReader registriert sich beim Config(MBean), um über Änderungen von Attributen benachrichtigt zu werden. Der Einfachheit halber wird dann immer die gesamte Config (Cache) ungültig und beim nächsten getConfig erneuert.

Zusätzlich registriert sich der ConfigReader noch beim Application Server direkt als Listener für unregister-Nachrichten, damit bei unregister/undeploy ebenfalls die Config ungültig wird.

Wichtig: Am Ende (der Benutzung) muß man sich als Listener mittels releaseConfigListeners wieder deregistrieren (z.B. in ejbRemove oder einem finally-Block).

* EJB normal deployen
* Alle class-Files aus de.isolvedit.config.category nach domain-dir/applications/mbeans kopieren
* folgendes Kommando (zu beachten: Nur via asadmin oder glassfish-GUI gesetzte Attribute werden gespeichert - bleiben also auch nach einem Serverneustart erhalten)
asadmin create-mbean --user adminuser --name de.isolvedit.config.category.Development de.isolvedit.config.category.Development --attributes SalesOrganisation=1100:DistributionChannel=10: Category=10:ShipCondition=ST:ChangeUser=unknown:Unit=ST: OrderTypeReturnFlag=ZRE: PositionTypeBuy=?:PositionTypeRent=!
Anmerkung: attr=value:attr=value ohne Leerzeichen(!)

So sieht das dann auf dem glassfish aus:


Hier schonmal die Ausgabe im Logfile:
[DemoBusiness.doSomething()] : start
[ConfigReader.connectConfigListener()] : add to notification listeners...
[ConfigReader.connectConfigListener()] : ...done:
[ConfigReader.getConfig()] : MBean from server
[ConfigReader.getConfig()] : MBean name: user:impl-class-name=de.isolvedit.config.category.Development, name=de.isolvedit.config.category.Development, server=server
[ConfigReader.getConfig()] : unit=ST
[ConfigReader.getConfig()] : shipCondition=ST
[ConfigReader.getConfig()] : positionTypeRent=FASEL
[ConfigReader.getConfig()] : category=10
[ConfigReader.getConfig()] : distributionChannel=10
[ConfigReader.getConfig()] : positionTypeBuy=BLA
[ConfigReader.getConfig()] : orderTypeReturnFlag=ZRE
[ConfigReader.getConfig()] : changeUser=unknown
[ConfigReader.getConfig()] : salesOrganisation=1100
[ConfigReader.handleNotification()] : notification message: config changed sequence: 10



Config

package de.isolvedit.config.category;

public interface ConfigMBean {
public String getSalesOrganisation();
public void setSalesOrganisation(String salesOrganisation);
public String getDistributionChannel();
public void setDistributionChannel(String distributionChannel);
public String getCategory();
public void setCategory(String category);
public String getShipCondition();
public void setShipCondition(String shipCondition);
public String getChangeUser();
public void setChangeUser(String changeUser);
public String getUnit();
public void setUnit(String unit);
public String getOrderTypeReturnFlag();
public void setOrderTypeReturnFlag(String orderTypeReturnFlag);
public String getPositionTypeBuy();
public void setPositionTypeBuy(String positionTypeBuy);
public String getPositionTypeRent();
public void setPositionTypeRent(String positionTypeRent);
}



ConfigBean

package de.isolvedit.config.category;

import javax.management.AttributeChangeNotification;
import javax.management.Notification;
import javax.management.NotificationBroadcasterSupport;

public abstract class Config extends NotificationBroadcasterSupport implements ConfigMBean {

public static final String CONFIG_CHANGED = "config changed";

// to set attributes use asadmin or glassfish-GUI !!
// the following properties are set to null, if deployed via glassfish!
// further details: http://docs.sun.com/app/docs/doc/820-4336/gbdzi?a=view

protected String category;
protected String changeUser;
protected String distributionChannel;
protected String orderTypeReturnFlag;
protected String positionTypeBuy;
protected String positionTypeRent;
protected String salesOrganisation;
protected String shipCondition;
protected String unit;

private long sequenceNumber = 1L;

// there is no abstract static -> so this is the method to override in subclasses!
public static Config DefaultConfig() {
return null;
};

public synchronized void configChanged() {
Notification n = new AttributeChangeNotification(this, sequenceNumber++, System.currentTimeMillis(),
CONFIG_CHANGED, "*", "String", null, null);
sendNotification(n);
}

public String getSalesOrganisation() {
return salesOrganisation;
}
public void setSalesOrganisation(String salesOrganisation) {
this.salesOrganisation = salesOrganisation;
configChanged();
}
public String getDistributionChannel() {
return distributionChannel;
}
public void setDistributionChannel(String distributionChannel) {
this.distributionChannel = distributionChannel;
configChanged();
}
public String getCategory() {
return category;
}
public void setCategory(String category) {
this.category = category;
configChanged();
}
public String getShipCondition() {
return shipCondition;
}
public void setShipCondition(String shipCondition) {
this.shipCondition = shipCondition;
configChanged();
}
public String getChangeUser() {
return changeUser;
}
public void setChangeUser(String changeUser) {
this.changeUser = changeUser;
configChanged();
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
configChanged();
}
public String getOrderTypeReturnFlag() {
return orderTypeReturnFlag;
}
public void setOrderTypeReturnFlag(String orderTypeReturnFlag) {
this.orderTypeReturnFlag = orderTypeReturnFlag;
configChanged();
}
public String getPositionTypeBuy() {
return positionTypeBuy;
}
public void setPositionTypeBuy(String positionTypeBuy) {
this.positionTypeBuy = positionTypeBuy;
configChanged();
}
public String getPositionTypeRent() {
return positionTypeRent;
}
public void setPositionTypeRent(String positionTypeRent) {
this.positionTypeRent = positionTypeRent;
configChanged();
}
}



Development

package de.isolvedit.config.category;

public class Development extends Config {

public static Development DefaultConfig() {
Development config = new Development();
config.setSalesOrganisation("1100");
config.setDistributionChannel("10");
config.setCategory("10");
config.setShipCondition("ST");
config.setChangeUser("unknown");
config.setUnit("ST");
config.setOrderTypeReturnFlag("ZRE");
config.setPositionTypeBuy("?!?");
config.setPositionTypeRent("!??");

return config;
}
}



Production

package de.isolvedit.config.category;

public class Production extends Config {

public static Production DefaultConfig() {
Production config = new Production();
config.setSalesOrganisation("1100");
config.setDistributionChannel("10");
config.setCategory("10");
config.setShipCondition("ST");
config.setChangeUser("uwEE");
config.setUnit("ST");
config.setOrderTypeReturnFlag("ZRE");
config.setPositionTypeBuy("BUY");
config.setPositionTypeRent("RNT");

return config;
}
}



ConfigReader

package de.isolvedit.config;

import java.util.Map;

import javax.management.AttributeChangeNotification;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.ObjectName;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.log4j.ConsoleAppender;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.log4j.PatternLayout;

import com.sun.appserv.management.base.Util;
import com.sun.enterprise.admin.server.core.jmx.AppServerMBeanServerFactory;

import de.isolvedit.config.category.Config;

public class ConfigReader implements NotificationListener {
public final static String MBEAN_SERVER_DELEGATE = "JMImplementation:type=MBeanServerDelegate";
public final static String MBEAN_UNREGISTERED = "JMX.mbean.unregistered";

/**
* static log4j-logger
*/
private static final Logger LOGGER = Logger.getLogger(ConfigReader.class);

private MBeanServerConnection server = null;
private Config configCache = null;
private boolean useCachedConfig = false; // force update
private boolean connectedToServerAsListener = false;
private ObjectName configObjectName = null;

public ConfigReader(Config defaultConfig) {
this.configCache = defaultConfig;
this.configObjectName = getObjectName();
// connect to glassfish
server = AppServerMBeanServerFactory.getMBeanServerInstance();
}

// for testing purposes only!
public ConfigReader(Config defaultConfig, MBeanServerConnection server) {
this.configCache = defaultConfig;
this.configObjectName = getObjectName();
this.server = server;
LOGGER.addAppender(new ConsoleAppender(new PatternLayout()));
LOGGER.setLevel(Level.ALL);
}

// react on config changes and mbean unregister events
// if attributes changed invalidate cached config (force update on next getConfig call)
// if config mbean is unregistered invalidate listenmode (force reconnect on next getConfig call)
public synchronized void handleNotification(Notification notification, Object handback) {
try {
if (notification instanceof AttributeChangeNotification) {
LOGGER.info("notification message: " + notification.getMessage() + " sequence: "
+ notification.getSequenceNumber());
if (Config.CONFIG_CHANGED.equals(notification.getMessage())) {
useCachedConfig = false; // force update
}
} else if (MBEAN_SERVER_DELEGATE.equals(notification.getSource().toString())
&& MBEAN_UNREGISTERED.equals(notification.getType()) && !server.isRegistered(configObjectName)) {
releaseConfigListener();
}
} catch (Exception e) {
LOGGER.error("caught exception", e);
}
}

// return config
// try to connect config mbean and register as attribute change and unregister listener
// try to read from config mbean else use default/cached version
public Config getConfig(boolean cachedVersion) {
connectConfigListener();
if (cachedVersion || useCachedConfig) {
LOGGER.info("MBean from cache");
} else {
LOGGER.info("MBean from server");
try {
LOGGER.info("MBean name: " + configObjectName);

@SuppressWarnings("unchecked")
Map properties = BeanUtils.describe(configCache);

for (String property : properties.keySet()) {
if (!property.equalsIgnoreCase("Class") && !property.equalsIgnoreCase("NotificationInfo")) {
Object value = server.getAttribute(configObjectName, initCap(property));
LOGGER.info(property + "=" + value.toString());
BeanUtils.setProperty(configCache, property, value);
}
}

useCachedConfig = true;

} catch (Exception e) {
LOGGER.error("caught exception", e);
useCachedConfig = false;
}
}
return configCache;
}

public boolean connectConfigListener() {
boolean result = false;
if (!connectedToServerAsListener) {
LOGGER.info("add to notification listeners...");
try {
server.addNotificationListener(configObjectName, this, null, null);
server.addNotificationListener(getMBeanServerDelegateObjectName(), this, null, null);
connectedToServerAsListener = true;
useCachedConfig = false; // force update
LOGGER.info("...done: ");
result = true;
} catch (Exception e) {
LOGGER.warn("...failed: " + e.getMessage());
e.printStackTrace();
result = false;
}
} else {
result = true;
}
return result;
}

public boolean releaseConfigListener() {
boolean result = false;
if (connectedToServerAsListener) {
try {
LOGGER.info("remove from notification listeners...");
if (server.isRegistered(configObjectName)) {
server.removeNotificationListener(configObjectName, this);
}
server.removeNotificationListener(getMBeanServerDelegateObjectName(), this);
LOGGER.info("...done");
result = true;
} catch (Exception e) {
LOGGER.error("...fail", e);
result = false;
}
connectedToServerAsListener = false;
} else {
result = true;
}
return result;
}

public static String initCap(String input) {
if (input == null || input.isEmpty()) {
return input;
}
return input.substring(0, 1).toUpperCase() + input.substring(1);
}

public ObjectName getObjectName() {
try {
return new ObjectName("user:impl-class-name=" + configCache.getClass().getName() + ",name="
+ configCache.getClass().getName() + ",server=server");
} catch (Exception e) {
return null;
}
}

public static ObjectName getMBeanServerDelegateObjectName() {
return (Util.newObjectName(MBEAN_SERVER_DELEGATE));
}
}



DemoBusiness

package de.isolvedit.business;

import de.isolvedit.config.category.Config;
import de.isolvedit.config.category.ConfigReader;
import de.isolvedit.config.category.Development;

@Stateless
public class DemoBusiness implements DemoBusinessLocal {

private static final Logger LOGGER = Logger.getLogger(ConfigReader.class);

private ConfigReader conf = new ConfigReader(Development.DefaultConfig());
//private ConfigReader conf = new ConfigReader(Production.DefaultConfig());

public String doSomething(){
LOGGER.info("start");
String result = null;
try {
try {
Config config = conf.getConfig(false);
result = config.getSalesOrganisation();
} catch (Exception e) {
LOGGER.error("caught exception", e);
}
} finally {
conf.releaseConfigListener();
}

return result;
}

}



Nachtrag: Spannend wäre sicher auch sich direkt als Listener für Attributänderungen beim Application Server zu registrieren ... Kommentare sind willkommen.
Nachtrag 2: Obiges funktioniert erst ab glassfish 2.1

2009-04-27

The Paradox of Choice - Why More Is Less

Verdammt guter Vortrag (englisch) zum Thema: Warum viele Optionen zu haben oft weniger Wert ist, als weniger Wahlfreiheit.

Was Freiheit in diesem Zusammenhang bedeutet wird gleich am Anfang aufbereitet.
Eine der Aussagen: Wenn Menschen zu viele Möglichkeiten haben, wählen sie lieber keine, als ggf. eine schlechte Variante. Und selbst wenn sie eine bessere gewählt haben, als sie dies aus einer kleineren Menge hätten tun können, fühlen sie sich oft schlechter. Und zwar, weil sie dank erhöhter Auswahl Perfektion erwarten, dies aber nicht "beweisbar" ist, solange sie nicht alle Möglichkeiten getestet haben (was unmöglicher wird, je größer die Vielfalt).
Dauer: ca 1 Stunde



Link: http://video.google.com/videoplay?docid=6127548813950043200&hl=de

Kleiner Nachtrag: Software-Entwickler/Architekten sollten sich von diesen allgemeinen IT unbelasteten Erkenntnissen beeinflussen lassen. Weniger ist oft mehr. Wobei die im letzten Teil angesprochene Verwendung von Agenten durchaus hilfreich sein kann (besonders schön das Beispiel mit den griechischen Restaurants).

Wieso Entscheider eine maximal Fünfpunkt Präsentation (in 48-Punkt Schrift) der abzuwägenden Risiken haben wollen, wird mir jetzt auch klarer ;)