Wer kennt das nicht? Man debuggt sich Zeile für Zeile durch den Code um Fehler in komplexen Algorithmen zu finden.
Das Auslesen von primitiven Datentypen oder Strings klappt ziemlich gut. Wenig hilfreich ist allerdings die Darstellung von Kalenderobjekten.

Mit Hilfe der in Eclipse angebotenen DetailFormater lassen sich Objekte im Debug-Modus beliebig formatieren. Für ein Objekt vom Typ “java.util.GregorianCalendar” könnte die Formatierung folgendermaßen aussehen: this.getTime().toString();
Ergebnis: Fri Jul 17 22:46:07 CEST 2009
Diese Darstellung ist dann schon deutlich lesbarer.
Auch für komplexere Datenstrukturen könnte es hilfreich sein, die wichtigen Informationen auf einem Blick zu sehen.
So funktioniert es

java.util.Calendar in das Feld “Qualified type name” fieldEnter this.getTime().toString()
Mit dem CoreMedia JavaEditor können Berechtigungsgruppen oder Rollen erstellt und Nutzern zugeordnet werden. Über die Gruppen können Berechtigungen sehr feingranular auf Dokumentebene eingestellt werden.

Bei der Authentifizierung wird das Nutzerobjekt mit seinen Berechtigungen (GrantedAuthority) geladen und im SecurityContext abgelegt. Für die Autorisierung kann in allen Schichten der Architektur darauf zugegriffen werden.
Wenn es sich um einfache rollenbasierte Autorisierung handelt, kann die authorize-Taglib aus dem Framework benutzt werden.
<authz:authorize ifAllGranted="ROLE_SUPERVISOR">
<td>
<A HREF="del.htm?id=<c:out value="${contact.id}"/>">Del</A>
</td>
</authz:authorize>
Die Autorisierung mit Berechtigungsgruppen (Bean-basierte Autorisierung) ist komplexer. Je nach Berechtigung des Nutzers sollen unterschiedliche Inhalte aus dem CMS dargestellt werden. Es empfiehlt sich, die Funktionalität für die Prüfung der Berechtigung in eine eigene Komponente (AuthorizationUtils) auszulagern, da diese auch z.B. bei den Controllern verwendet werden kann.
AuthorizationUtils
public class AuthorizationUtils{
...
public boolean isAccessAllowed(Content content){
GrantedAuthority[] grants = null;
if (SecurityContextHolder.getContext().getAuthentication() != null) {
grants = SecurityContextHolder
.getContext().getAuthentication().getAuthorities();
}
int length = (grants != null) ? grants.length : 0;
String[] groupnames = new String[length];
for (int i = 0; i < length; i++) {
groupnames[i] = grants[i].getAuthority();
}
LinkedList groups = getGroups(groupnames);
if (groups != null && getAccessControl().mayRead(content, groups)) {
isAccessAllowed = true;
} else {
isAccessAllowed = false;
}
return isAccessAllowed;
}
private LinkedList getGroups(String[] groupNames) {
LinkedList groups = new LinkedList();
for (int i = 0; groupNames != null && i < groupNames.length; i++) {
Group group = userRepository.getGroupByName(groupName, null);
if (group != null) {
groups.add(group);
}
}
return groups;
}
private AccessControl getAccessControl() throws MipException {
AccessControl accessControl = getRepository().getAccessControl();
return accessControl ;
}
...
}
Authorize-Tag Library
public class Authorize extends TagSupport {
private ContentBean contentBean;
public int doStartTag() {
if (null == bean) {
return SKIP_BODY;
}
ApplicationContext context = getContext(pageContext);
AuthorizationUtils authorizationUtils = (AuthorizationUtils)
context.getBean("authorizationUtils");
try {
if (authorizationUtils.isAccessAllowed(bean.getContent(),
null, (HttpServletRequest) pageContext
.getRequest())) {
return EVAL_BODY_INCLUDE;
}
} catch (Exception e) {
e.printStackTrace();
}
return SKIP_BODY;
}
protected ApplicationContext getContext(PageContext pageContext) {
ServletContext servletContext = pageContext.getServletContext();
return WebApplicationContextUtils
.getRequiredWebApplicationContext(servletContext);
}
public ContentBean getContentBean() {
return contentBean;
}
public void setContentBean(ContentBean contentBean) {
contentBean = contentBean;
}
}
Einbindung im Template
<mse:authorize contentBean="${self}">
<c:out value="${self.name}" />
</mse:authorize>
Auch im Controller kann man den bereits vorgestellten Bean-basierten Autorisierungsmechanismus verwenden, um z.B. komplette Seitenaufrufe zu verhindern.
SecureContentViewController
public class SecureContentViewController extends ContentViewController {
private AuthorizationUtils authorizationUtils;
protected Object resolveBean(String controllerPathInfo,
Map parameters, HttpServletRequest request) {
...
}
protected String resolveView(String controllerPathInfo,
Map parameters, HttpServletRequest request) {
...
}
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
String pathInfo = controllerPathInfo(HttpServletRequest request);
Object bean = resolveBean(pathInfo, request.getParameterMap(), request);
ContentBean contentBean = (ContentBean) bean;
if (!authorizationUtils.isAccessAllowed(contentBean.getContent())) {
// Fehlerbehandlung
return new ModelAndView(new RedirectView(LOGIN_URL));
}
return super.handleRequestInternal(request, response);
}
...
}
Das Thema methodenbasierte Security kann man elegant mit den Mitteln der aspektorientierten Programmierung (AOP) lösen. Neben Frameworks wie aspectJ bietet auch Spring mit Dynamic Proxys eine AOP-Implementierung.
CheckPermissionInterceptor
public class CheckPermissionInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation aMethodInvocation) throws Throwable {
initSecureMethodsMap();
String currentMethode = aMethodInvocation.getMethod().getName();
// ist SecureMethode
if (secureMethodsMap.containsKey(currentMethode)) {
Authentication authentication = SecurityContextHolder.getContext()
.getAuthentication();
GrantedAuthority[] auth = authentication.getAuthorities();
int length = (auth != null) ? auth.length : 0;
String[] roleNames = new String[length];
for (int i = 0; auth != null && i < auth.length; i++) {
roleNames[i] = auth[i].getAuthority();
}
String roles = (String) secureMethodsMap.get(currentMethode);
if(!checkPermission(roles, roleNames){
// Keine Rechte
throw new AccessDeniedException("User hat nicht genügend Rechte");
}
}
return aMethodInvocation.proceed();
}
private boolean checkPermission(String allowedRoles, String[] availableRoles){
for (int i = 0; i < availableRoles.length; i++) {
if(allowedRoles.indexOf(availableRoles[i])!=-1){
return true;
}
}
return false;
}
}
In der Spring-Konfiguration wird ein Dynamic Proxy mit dem Namen des Services konfiguriert. Der Proxy kennt den Service unter der Property target. Die Service-Aufrufe werden so durch den Proxy abgefangen. Nun kommt der Interceptor ins Spiel. Er prüft für konfigurierte Methoden die Berechtigung. Ist alles o.k., wird die Anfrage an den eigentlichen Service delegiert.
Spring-Konfiguration
<beans>
<bean id="aServiceTarget" class="de.mse.business.ServiceImpl">
...
</bean>
<bean id="aService"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces">
<value>de.mse.business.IService</value>
</property>
<property name="target">
<ref bean="aServiceTarget"/>
</property>
<property name="interceptorNames">
<list>
<value>checkPermissionInterceptor</value>
</list>
</property>
</bean>
<bean id="checkPermissionInterceptor"
class="de.mse.security.CheckPermissionInterceptor">
<property name="secureMethods">
<value>
methode1=ADMIN
methode2=ADMIN, REDAKTEUR
...
</value>
</property>
</bean>
</beans>
Erstellung eines CoreMedia-AuthenticationProviders für das Spring Security Framework
Neben dem Aufbau von geschlossenen Benutzergruppen (GBGs) im CMS kann es auch andere Gründe geben, das CoreMedia UserRepository für die Authentifizierung von Nutzern zu verwenden. Das Spring Security Framework (ehemals ACEGI Security) bietet Funktionalitäten für die Authentifizierung und Autorisierung von Nutzern. Im Folgenden Klassendiagramm sind die Interfaces und Klassen für die Authentifizierung dargestellt.

Standardmäßig sind im Framework bereits verschiedene Implementierungen vorhanden (z.B. für Datenbanken, LDAP u.a.). Mit wenig Aufwand kann man eigene Implementierungen wie die gegen das CoreMedia UserRepository implementieren.
Der AuthenticationManager ist der zentrale Einstieg für die Authentifizierung. Er kann mehrere verschiedene AuthenticationProvider verwalten. Über das Authentication-Objekt kann die Authentifizierung nur durch gewünschte Provider erfolgen (AuthenticationProvider#supports(Class authentication)).
Der UserDetailsService lädt das User-Objekt aus dem jeweiligen Repository per Namen (#loadUserByUsername(String username)). Der Provider überprüft das Passwort mit der Methode additionalAuthenticationChecks(...).
public class CMUserDao implements UserDetailsService{
...
public UserDetails loadUserByUsername(String username)
throws UsernameNotFoundException, DataAccessException {
UserRepository userRepository = capConnection.getUserRepository();
org.acegisecurity.userdetails.User user = null;
try {
Repository repository = ContentRepositoryFactory.instance().getContentRepository();
User cmsUser = userRepository.getUserByName(username);
Collection < Group > groups = new ArrayList < Group >();
if (cmsUser != null) {
groups = cmsUser.getGroups();
} else {
throw new UsernameNotFoundException(username);
}
Collection < GrantedAuthority > authorities = new ArrayList < GrantedAuthority >();
if (groups != null) {
for (Group group : groups) {
authorities.add(new GrantedAuthorityImpl(group.getName()));
}
}
user = new org.acegisecurity.userdetails.User(username, "", true, true, true, true,
authorities.toArray(new GrantedAuthority[0]));
} catch (InvalidLoginException e) {
throw new BadCredentialsException(e.getMessage(), e);
} catch (LicensesExceededException e) {
new BadCredentialsException(e.getMessage(), e);
} catch (ConnectionNotOpenException e) {
new BadCredentialsException(e.getMessage(), e);
}
return user; } ... }
public class CMAuthenticationProvider extends
AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider {
...
protected void additionalAuthenticationChecks(UserDetails aUserDetails,
UsernamePasswordAuthenticationToken aAuthenticationToken)
throws AuthenticationException {
String username = aUserDetails.getUsername();
String password = aAuthenticationToken.getCredentials().toString();
boolean isValid = capConnection.isValidLogin(username, null, password);
if (!isValid) {
throw new BadCredentialsException(messages.getMessage(
"AbstractUserDetailsAuthenticationProvider.badCredentials",
"Bad credentials"), aUserDetails);
}
}
...
}
Benutzt man den AuthenticationProcessingFilter aus dem Framework wird nach erfolgreicher Authentifizierung das Authentication-Objekt in den SecurityContext geladen, einer ThreadLocal-Variable. Benutzt man einen eigenen Controller, muss man dies von Hand machen.
auth = authenticationManager.authenticate(...);
SecurityContextHolder.getContext().setAuthentication(auth);
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
...
</bean>
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="cmsDaoAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="cmsDao" class="de.mse.authentication.CMUserDao"/>
<bean id="cmsDaoAuthenticationProvider"
class="de.mse.authentication.CMAuthenticationProvider">
<property name="userDetailsService">
<ref bean="cmsDao"/>
</property>
</bean>
Links
OutOfMemory ist eines der am häufigsten auftretenden Probleme bei Java-basierten Webapplikationen. Die Ursachen dafür sind genau so vielschichtig wie die Möglichkeiten der Analyse. Ziel dieses Artikels ist es, sowohl die Grundlagen wie auch mögliche Lösungswege zu zeigen.
Bevor man jetzt leichtfertig die Software umprogrammiert (da man ja genau weiß, wo das Problem liegt), sollte man mit einer ausführlichen Analyse beginnen. Nicht selten kommt es vor, dass Software (Parameter der JVM) oder sogar das Betriebssystem nicht korrekt konfiguriert sind.
Die Process Size ist der maximal verfügbare Speicher für den Prozess. Dieser ist abhängig von der Hardware und vom Betriebssystem. In einer 32 Bit-Architektur kann die Process Size maximal 4 GB sein. Für einen Java-Prozess gibt es folgende Bereiche, die im Speicher des Prozesses liegen (siehe Abbildung 1):

Abbildung 1: Schematischer Aufbau der Speicherverteilung eines Java-Prozesses
Der Speicher für den Native Code ist nicht explizit einstellbar. Er ergibt sich aus der Differenz zwischen Process Size, Heap und Permanent Generation. Dieser Speicher darf nicht zu knapp bemessen sein, denn es kann auch OutOfMemory im Native Code geben.
Die Java Heap Size gibt die Größe des Speichers an, der für die Erzeugung und Verwaltung der Java-Objekte verwendet werden kann. Die Größe des Heaps ist begrenzt. Wird keine Größe explizit angegeben, wird die Defaulteinstellung verwendet. Diese variiert von Betriebssystem zu Betriebssystem. Wenn man wirklich sicher gehen möchte, stellt man die Größe explizit ein:
-Xms[Bytes]m : initiale Größe des Heaps beim Starten des Prozesses-Xmx[Bytes]m : maximale Größe des Heaps über den gesamten LebenszeitraumDer Heap teilt sich in zwei große Bereiche: der New Generation und der Old Generation (siehe Abbildung 2).
Wie die Namen schon vermuten lassen, befinden sich in der New Generation die ganzen neuen, jungen und kurzlebigen Java-Objekte und in der Old Generation die langlebigen Java-Objekte.

Abbildung 2: Aufteilung der Speicherbereiche im Heap
Das Verhältnis zwischen New und Old Generation ist aus Speichersicht der größte Unterschied zwischen einer Desktop- und einer Serveranwendung. Eine Desktopanwendung läuft in der Regel einen Arbeitstag, also ca. 8 h. Eine Serveranwendung läuft 24 h, 7 Tage die Woche und das mehrere Monate. Daran kann man schon erkennen, dass es bei einer Desktopanwendung mehr jüngere Objekte und bei einer Serveranwendung mehr ältere Objekte gibt. Mit dem Servermodus stellt man das Verhältnis zwischen New und Old Generation auf 1:3 ein.
-server :Servermodus für die VMDie Permanent Generation wird von der JVM für das Laden der Klassen durch den ClassLoader verwendet. Normalerweise ist die Defaulteinstellung der Größe ausreichend. Wenn allerdings viele Klassen dynamisch durch die Applikation geladen werden (z.B. durch Reflection), kann die Defaulteinstellung zu Problemen führen. Auch die Verwendung von Persistenz- oder Caching-Frameworks führt zu größerem Speicherverbrauch in der Permanent Generation.
Über folgende Parameter kann die Defaultgröße der Permanent Generation angepasst werden:
-XX:PermSize=[Bytes]m : initiale Größe der Permanent Generation-XX:MaxPermSize=[Bytes]m : maximale Größe der Permanent GenerationMan unterscheidet bei der Garbage Collection in die („einfache“) Garbage Collection und in die Full Garbage Collection. Bei der Garbage Collection werden nicht mehr referenzierte Objekte aus der New Generation freigegeben. Des Weiteren werden noch „lebende“ Objekte, die durch mehrere Garbage Collection nicht freigegeben wurden, in die Old Generation kopiert. Bei einer Full Garbage Collection werden auch Objekte aus der Old Generation aufgeräumt.
Eine Full Garbage Collection ist sehr zeitintensiv (bei 2 GB Heap Size = mehrere Sekunden). Während dieser Zeit reagiert die Applikation nicht mehr.
Ist die Heap Size zu gering eingestellt, findet eine Full Garbage Collection nach der anderen statt. Dies führt zu einer sehr schlechten Performance und zu langen Antwortszeiten.
In einer Multiprozessor-Maschine ist es deshalb ratsam, die Garbage Collection („einfache“ und Full) parallel arbeiten zu lassen.
-XX:+UseParallelGC: parallele Garbage Collection und Full Garbage CollectionNachdem in diesem Artikel die Grundlagen erläutert wurden, werden im nächsten Artikel konkrete Analyse-Möglichkeiten besprochen.
Releaseparty at Atlassian? Confluence 3.2 BETA and 3.1.2 with soms bugfixes were released yesterday. [...]
Tino Schmidt's Vortrag zu Enterprise Mashups auf der webciety, 4.3 Remix the Web http://bit.ly/d26rtA [...]
Am 12.05.2010 erscheint SharePoint 2010 & Office 2010 - Wir können es nicht erwarten - http://bit.ly/9Oi3hO [...]
jetzt online! vortrag @jeos zu communote - enterprise #microblogging (ab min. 6:30) http://bit.ly/bONP34 #webciety #cebit [...]