<rdf:RDF
    xmlns:s='http://snipsnap.org/rdf/snip-schema#'
    xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
    xml:base='http://www.stefanrufer.ch/snipsnap/rdf'>
    <s:Snip rdf:about='http://www.stefanrufer.ch/snipsnap/rdf#Virtual+Brain/Java/Debug-Appender'
         s:cUser='stefan'
         s:oUser='stefan'
         s:mUser='stefan'>
        <s:name>Virtual Brain/Java/Debug-Appender</s:name>
        <s:content>1 Log4j Appender that supports a DEBUG dump on ERRORs&#xD;&#xA;&#xD;&#xA;Logfiles with too many debug statements are too noisy and not useful in production - sometimes they also grow too big. However, many developers wish to have some more debugging information if an error occured. &#xD;&#xA;&#xD;&#xA;The idea to implement this requirement with a ringbuffer that stores the last N log statements is not new, but I haven&apos;t found a free implementation. &#xD;&#xA;&#xD;&#xA;Here it is :-)&#xD;&#xA;&#xD;&#xA;1.1 Download and Source&#xD;&#xA;&#xD;&#xA;Find all the source in the attached ZIP file. If you are only looking for the binaries, you can use the attached JAR file.&#xD;&#xA;&#xD;&#xA;1.1 Configuration&#xD;&#xA;&#xD;&#xA;First a quick look on how to configure this in the log4j.xml file. This is an example configuration file for the DebugSavingAppenderAttachable.&#xD;&#xA;&#xD;&#xA;It defines a standard appender for the console to show you the log results. This console appender is then added to an appender called &quot;DUMPER&quot;, this is the one that will save DEBUG statements and dump them if an ERROR occurs.&#xD;&#xA;&#xD;&#xA;Important is the &lt;root&gt; section where the root logger is defined, it must only contain the DUMPER appender.&#xD;&#xA;&#xD;&#xA;{code:xml}&#xD;&#xA;  &lt;appender name=&quot;CONSOLE&quot; class=&quot;org.apache.log4j.ConsoleAppender&quot;&gt;&#xD;&#xA;    &lt;param name=&quot;Target&quot; value=&quot;System.out&quot;/&gt;&#xD;&#xA;    &lt;param name=&quot;Threshold&quot; value=&quot;DEBUG&quot;/&gt;&#xD;&#xA;&#xD;&#xA;    &lt;layout class=&quot;org.apache.log4j.PatternLayout&quot;&gt;&#xD;&#xA;      &lt;param name=&quot;ConversionPattern&quot; value=&quot;%d{ABSOLUTE} %-5p [%c{1}] %m%n&quot;/&gt;&#xD;&#xA;    &lt;/layout&gt;&#xD;&#xA;  &lt;/appender&gt;&#xD;&#xA;&#xD;&#xA;  &lt;appender name=&quot;DUMPER&quot; class=&quot;ch.stefanrufer.log4jextension.DebugSavingAppenderAttachable&quot;&gt;&#xD;&#xA;    &lt;param name=&quot;Threshold&quot; value=&quot;INFO&quot;/&gt;&#xD;&#xA;    &lt;param name=&quot;DebugDumpThreshold&quot; value=&quot;ERROR&quot;/&gt;&#xD;&#xA;    &lt;param name=&quot;RingbufferSize&quot; value=&quot;10&quot;/&gt;&#xD;&#xA;    &lt;appender-ref ref=&quot;CONSOLE&quot;/&gt;&#xD;&#xA;  &lt;/appender&gt;&#xD;&#xA;&#xD;&#xA;  &lt;root&gt;&#xD;&#xA;     &lt;appender-ref ref=&quot;DUMPER&quot;/&gt;&#xD;&#xA;  &lt;/root&gt;&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;1.1 Code&#xD;&#xA;&#xD;&#xA;Then the implementation of the DebugSavingAppenderAttachable:&#xD;&#xA;{code:java}&#xD;&#xA;/*&#xD;&#xA; * This is free software. Use it, re-distribute it, change it but don&apos;t complain.&#xD;&#xA; * &#xD;&#xA; * Author: Stefan Rufer, http://www.stefanrufer.ch&#xD;&#xA; */&#xD;&#xA;package ch.stefanrufer.log4jextension;&#xD;&#xA;&#xD;&#xA;import java.util.Enumeration;&#xD;&#xA;import java.util.Iterator;&#xD;&#xA;&#xD;&#xA;import org.apache.log4j.Appender;&#xD;&#xA;import org.apache.log4j.AppenderSkeleton;&#xD;&#xA;import org.apache.log4j.Level;&#xD;&#xA;import org.apache.log4j.Logger;&#xD;&#xA;import org.apache.log4j.Priority;&#xD;&#xA;import org.apache.log4j.helpers.AppenderAttachableImpl;&#xD;&#xA;import org.apache.log4j.spi.AppenderAttachable;&#xD;&#xA;import org.apache.log4j.spi.LoggingEvent;&#xD;&#xA;&#xD;&#xA;/**&#xD;&#xA; * This combined {@link Appender}/{@link AppenderAttachable} is able to store log messages in a&#xD;&#xA; * ring buffer and dump them if a severe log event occurred. A typical use case is to dump the&#xD;&#xA; * past few DEBUG messages if an ERROR log is detected. A typical configuration in a log4j.xml&#xD;&#xA; * file may look like this:&#xD;&#xA; * &#xD;&#xA; * &lt;pre&gt;&#xD;&#xA; * &lt;code&gt;&#xD;&#xA; * &amp;lt;appender name=&amp;quot;DUMPER&amp;quot; &#xD;&#xA; *       class=&amp;quot;ch.stefanrufer.log4jextension.DebugSavingAppenderAttachable&amp;quot;&amp;gt;&#xD;&#xA; *   &amp;lt;param name=&amp;quot;Threshold&amp;quot; value=&amp;quot;INFO&amp;quot;/&amp;gt;&#xD;&#xA; *   &amp;lt;param name=&amp;quot;DebugDumpThreshold&amp;quot; value=&amp;quot;ERROR&amp;quot;/&amp;gt;&#xD;&#xA; *   &amp;lt;param name=&amp;quot;RingbufferSize&amp;quot; value=&amp;quot;10&amp;quot;/&amp;gt;&#xD;&#xA; *   &amp;lt;appender-ref ref=&amp;quot;FILE&amp;quot;/&amp;gt;&#xD;&#xA; * &amp;lt;/appender&amp;gt;&#xD;&#xA; * &lt;/code&gt;&#xD;&#xA; * &lt;/pre&gt;&#xD;&#xA; * &#xD;&#xA; * This means that log messages with {@link Level#INFO} and {@link Level#WARN} will be logged as&#xD;&#xA; * usual. A log event with {@link Level#ERROR} or more severe will also be logged as usual but&#xD;&#xA; * right after the last 10 messages with level &lt;b&gt;less&lt;/b&gt; severe than {@link Level#INFO} will be&#xD;&#xA; * dumped to the appender named &quot;FILE&quot;.&#xD;&#xA; */&#xD;&#xA;public class DebugSavingAppenderAttachable extends AppenderSkeleton implements AppenderAttachable {&#xD;&#xA;&#xD;&#xA;  /** Default size of the underlying ring buffer used to store non-logged statements. */&#xD;&#xA;  private static final int RINGBUFFER_DEFAULT_SIZE = 100;&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * The delegate object where the {@link AppenderAttachable} operations are implemented.&#xD;&#xA;   */&#xD;&#xA;  private AppenderAttachableImpl delegate = new AppenderAttachableImpl();&#xD;&#xA;&#xD;&#xA;  /** The level when to start a debug dump. Defaults to {@link Level#ERROR}. */&#xD;&#xA;  private Level debugDumpThreshold = Level.ERROR;&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * The size of the ring buffer used to keep unlogged messages. Defaults to&#xD;&#xA;   * {@link #RINGBUFFER_DEFAULT_SIZE}.&#xD;&#xA;   */&#xD;&#xA;  private int ringbufferSize = RINGBUFFER_DEFAULT_SIZE;&#xD;&#xA;&#xD;&#xA;  /** The ring buffer used internally for &quot;saved&quot; log statements. */&#xD;&#xA;  private Ringbuffer&lt;LoggingEvent&gt; ringbuffer = null;&#xD;&#xA;&#xD;&#xA;  private Level threshold = Level.INFO;&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Delegates the given log event to all loggers referenced by this {@link AppenderAttachable} if&#xD;&#xA;   * the log level of the log event is more severe than the configured threshold. If it is less&#xD;&#xA;   * severe, the log event goes into a ring buffer. If the log level of the log event is the same&#xD;&#xA;   * or more severe as {@link #debugDumpThreshold} the ring buffer with the formerly stored debug&#xD;&#xA;   * messages will be dumped and cleared.&#xD;&#xA;   * &#xD;&#xA;   * @param event the log event to be checked and maybe logged&#xD;&#xA;   */&#xD;&#xA;  @Override&#xD;&#xA;  protected void append(LoggingEvent event) {&#xD;&#xA;    if (threshold.toInt() &gt; event.getLevel().toInt()) {&#xD;&#xA;      if (ringbuffer == null) {&#xD;&#xA;        ringbuffer = new Ringbuffer&lt;LoggingEvent&gt;(ringbufferSize);&#xD;&#xA;      }&#xD;&#xA;      ringbuffer.add(event);&#xD;&#xA;    } else {&#xD;&#xA;      delegate.appendLoopOnAppenders(event);&#xD;&#xA;      if (debugDumpThreshold.toInt() &lt;= event.getLevel().toInt()) {&#xD;&#xA;        dumpAndClearRingbuffer();&#xD;&#xA;      }&#xD;&#xA;    }&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * This stores the given threshold internally and does not pass this on to the child loggers of&#xD;&#xA;   * this appender attachable.&#xD;&#xA;   * &#xD;&#xA;   * @param threshold the new threshold&#xD;&#xA;   */&#xD;&#xA;  @Override&#xD;&#xA;  public void setThreshold(Priority threshold) {&#xD;&#xA;    this.threshold = Level.toLevel(threshold.toString());&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Dumps the content of the ring buffer (if available) to all appenders referenced by this&#xD;&#xA;   * {@link AppenderAttachable}. After the dump, the ring buffer is cleared.&#xD;&#xA;   */&#xD;&#xA;  private void dumpAndClearRingbuffer() {&#xD;&#xA;    if (ringbuffer == null) {&#xD;&#xA;      return;&#xD;&#xA;    }&#xD;&#xA;&#xD;&#xA;    delegate.appendLoopOnAppenders(new LoggingEvent(&quot;ch.stefanrufer.log4jextension&quot;,&#xD;&#xA;        Logger.getLogger(this.getClass()), Priority.INFO, &quot;Dumping the last &quot;&#xD;&#xA;            + ringbuffer.getCount() + &quot; log messages with level &quot; + threshold&#xD;&#xA;            + &quot; or less important because a log message with level &quot; + debugDumpThreshold&#xD;&#xA;            + &quot; or more severe was detected. Hope it helps for debugging...&quot;, null));&#xD;&#xA;    Iterator&lt;LoggingEvent&gt; it = ringbuffer.iterator();&#xD;&#xA;    while (it.hasNext()) {&#xD;&#xA;      delegate.appendLoopOnAppenders(it.next());&#xD;&#xA;    }&#xD;&#xA;    ringbuffer.clear();&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public void close() {&#xD;&#xA;    Enumeration&lt;Appender&gt; en = delegate.getAllAppenders();&#xD;&#xA;    while (en.hasMoreElements()) {&#xD;&#xA;      en.nextElement().close();&#xD;&#xA;    }&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Gets the debugDumpThreshold.&#xD;&#xA;   * &#xD;&#xA;   * @return the debugDumpThreshold&#xD;&#xA;   */&#xD;&#xA;  public Level getDebugDumpThreshold() {&#xD;&#xA;    return debugDumpThreshold;&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Sets the debugDumpThreshold.&#xD;&#xA;   * &#xD;&#xA;   * @param debugDumpThreshold the debugDumpThreshold to set&#xD;&#xA;   */&#xD;&#xA;  public void setDebugDumpThreshold(Level debugDumpThreshold) {&#xD;&#xA;    this.debugDumpThreshold = debugDumpThreshold;&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Gets the ringbufferSize.&#xD;&#xA;   * &#xD;&#xA;   * @return the ringbufferSize&#xD;&#xA;   */&#xD;&#xA;  public int getRingbufferSize() {&#xD;&#xA;    return ringbufferSize;&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Sets the ringbufferSize.&#xD;&#xA;   * &#xD;&#xA;   * @param ringbufferSize the ringbufferSize to set&#xD;&#xA;   */&#xD;&#xA;  public void setRingbufferSize(int ringbufferSize) {&#xD;&#xA;    this.ringbufferSize = ringbufferSize;&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public void addAppender(Appender newAppender) {&#xD;&#xA;    delegate.addAppender(newAppender);&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public Enumeration&lt;Appender&gt; getAllAppenders() {&#xD;&#xA;    return delegate.getAllAppenders();&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public Appender getAppender(String name) {&#xD;&#xA;    return delegate.getAppender(name);&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public boolean isAttached(Appender appender) {&#xD;&#xA;    return delegate.isAttached(appender);&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public void removeAllAppenders() {&#xD;&#xA;    delegate.removeAllAppenders();&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public void removeAppender(Appender appender) {&#xD;&#xA;    delegate.removeAppender(appender);&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public void removeAppender(String name) {&#xD;&#xA;    delegate.removeAppender(name);&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * {@inheritDoc}&#xD;&#xA;   */&#xD;&#xA;  public boolean requiresLayout() {&#xD;&#xA;    return false;&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;}&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;And a helper class that is needed:&#xD;&#xA;{code:java}&#xD;&#xA;/*&#xD;&#xA; * This is free software. Use it, re-distribute it, change it but don&apos;t complain.&#xD;&#xA; * &#xD;&#xA; * Author: Stefan Rufer, http://www.stefanrufer.ch&#xD;&#xA; */&#xD;&#xA;package ch.stefanrufer.log4jextension;&#xD;&#xA;&#xD;&#xA;import java.util.ArrayList;&#xD;&#xA;import java.util.Iterator;&#xD;&#xA;&#xD;&#xA;/**&#xD;&#xA; * Simple ring buffer implementation that can store a fixed amount of elements in an internal&#xD;&#xA; * buffer. If the maximum size is reached, the element added first will be overwritten.&#xD;&#xA; * No elements can be removed from the ring buffer.&#xD;&#xA; * &lt;p&gt;&#xD;&#xA; * This implementation is not thread safe.&#xD;&#xA; */&#xD;&#xA;public class Ringbuffer&lt;T&gt; {&#xD;&#xA;&#xD;&#xA;  private ArrayList&lt;T&gt; buffer = null;&#xD;&#xA;&#xD;&#xA;  private int last = -1;&#xD;&#xA;  private int size = 0;&#xD;&#xA;  private boolean overflow = false;&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Create a new ring buffer with the given size. The size can not be changed later on and is&#xD;&#xA;   * fixed.&#xD;&#xA;   * &#xD;&#xA;   * @param size fixed size of the ring buffer&#xD;&#xA;   * @throws IllegalArgumentException if size is &amp;lt; 1&#xD;&#xA;   */&#xD;&#xA;  public Ringbuffer(int size) {&#xD;&#xA;    if (size &lt; 1) {&#xD;&#xA;      throw new IllegalArgumentException(&quot;Ringbuffer with size &lt; 1 can not be created&quot;);&#xD;&#xA;    }&#xD;&#xA;&#xD;&#xA;    this.size = size;&#xD;&#xA;    buffer = new ArrayList&lt;T&gt;(size);&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Add the given element to the ring buffer. It will be appended at the end of the internal&#xD;&#xA;   * buffer or replace the oldest element if the maximum capacity was reached.&#xD;&#xA;   * &#xD;&#xA;   * @param element the element to add&#xD;&#xA;   */&#xD;&#xA;  public void add(T element) {&#xD;&#xA;    last++;&#xD;&#xA;    last = last % size;&#xD;&#xA;    if (buffer.size() &lt; size) {&#xD;&#xA;      buffer.add(element);&#xD;&#xA;    } else {&#xD;&#xA;      // we are overwriting elements&#xD;&#xA;      overflow = true;&#xD;&#xA;      buffer.set(last, element);&#xD;&#xA;    }&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Clear the ring buffer. All elements currently in the ring buffer will be removed.&#xD;&#xA;   */&#xD;&#xA;  public void clear() {&#xD;&#xA;    last = -1;&#xD;&#xA;    overflow = false;&#xD;&#xA;    buffer.clear();&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Get all the elements that are currently stored in the ring buffer. The order&#xD;&#xA;   * of the added elements is kept and the resulting list will contain the &quot;oldest&quot; &#xD;&#xA;   * element first in list and the last one added as last in list.&#xD;&#xA;   * The returned list will have a size equal to {@link #getCount()}.&#xD;&#xA;   * &#xD;&#xA;   * @return all the elements in the ring buffer in correct order&#xD;&#xA;   */&#xD;&#xA;  public ArrayList&lt;T&gt; elements() {&#xD;&#xA;    ArrayList&lt;T&gt; result = new ArrayList&lt;T&gt;(size);&#xD;&#xA;    &#xD;&#xA;    // maybe there would be an elegant solution to combine these two branches, but&#xD;&#xA;    // this works and is easy to understand&#xD;&#xA;    if (overflow) {&#xD;&#xA;      // we had an overflow =&gt; start the elements list one after last&#xD;&#xA;      for (int i = last + 1; i &lt; last + 1 + size; i++) {&#xD;&#xA;        result.add(buffer.get(i % size));&#xD;&#xA;      }&#xD;&#xA;    } else {&#xD;&#xA;      // without overflow, just add all the elements in the buffer to the result&#xD;&#xA;      for (int i = 0; i &lt; buffer.size(); i++) {&#xD;&#xA;       result.add(buffer.get(i)); &#xD;&#xA;      }&#xD;&#xA;    }&#xD;&#xA;    &#xD;&#xA;    return result;&#xD;&#xA;  }&#xD;&#xA;  &#xD;&#xA;  /**&#xD;&#xA;   * Get an iterator of all the elements that are currently stored in the ring buffer. The order&#xD;&#xA;   * of the added elements is kept and the resulting iterator will first present the oldest&#xD;&#xA;   * element of the ring buffer.&#xD;&#xA;   * &#xD;&#xA;   * @return Iterator for all the elements in the ring buffer in correct order&#xD;&#xA;   */&#xD;&#xA;  public Iterator&lt;T&gt; iterator() {&#xD;&#xA;    return elements().iterator();&#xD;&#xA;  }&#xD;&#xA;&#xD;&#xA;  /**&#xD;&#xA;   * Get the current count of elements in the ring buffer. The minimum &#xD;&#xA;   * count returned is 0 the maximum is the size of the ring buffer.&#xD;&#xA;   * &#xD;&#xA;   * @return count of elements in the ring buffer&#xD;&#xA;   */&#xD;&#xA;  public int getCount() {&#xD;&#xA;    return buffer.size();&#xD;&#xA;  }&#xD;&#xA;  &#xD;&#xA;  /**&#xD;&#xA;   * Get the size of the ring buffer.&#xD;&#xA;   * &#xD;&#xA;   * @return the size&#xD;&#xA;   */&#xD;&#xA;  public int getSize() {&#xD;&#xA;    return size;&#xD;&#xA;  }&#xD;&#xA;}&#xD;&#xA;{code}&#xD;&#xA;&#xD;&#xA;1 History&#xD;&#xA;&#xD;&#xA; - 2007-10-08: thanks to Joachim Hagger for pointing out a bug in the initialization of the Ringbuffer (added size validation that was documented in JavaDoc but missing in the code)&#xD;&#xA; - 2011-02-02: thanks to Roland Weiss for pointing out a bug in the Ringbuffer (clear method needs to set overflow to false)</s:content>
        <s:mTime>2011-02-02 13:43:17.281</s:mTime>
        <s:cTime>2007-10-04 08:46:10.887</s:cTime>
        <s:comments
             rdf:type='http://www.w3.org/1999/02/22-rdf-syntax-ns#Bag'/>
        <s:snipLinks>
            <rdf:Bag>
                <rdf:li rdf:resource='http://www.stefanrufer.ch/snipsnap/rdf#Virtual Brain/Java'/>
                <rdf:li rdf:resource='#snipsnap-index'/>
                <rdf:li rdf:resource='http://www.stefanrufer.ch/snipsnap/rdf#Virtual Brain'/>
                <rdf:li>
                    <s:Snip rdf:about='http://www.stefanrufer.ch/snipsnap/rdf#Virtual+Brain/Java/Debug-Appender'>
                        <s:attachments>
                            <rdf:Bag>
                                <rdf:li>
                                    <s:Attachment rdf:about='http://www.stefanrufer.ch/snipsnap/space/Virtual+Brain/Java/Debug-Appender/log4jextension-1.0.0-SNAPSHOT.jar'
                                         s:fileName='log4jextension-1.0.0-SNAPSHOT.jar'
                                         s:contentType='application/java-archive'
                                         s:size='5616'>
                                        <s:date>Mon Oct 08 14:06:05 CEST 2007</s:date>
                                    </s:Attachment>
                                </rdf:li>
                                <rdf:li>
                                    <s:Attachment rdf:about='http://www.stefanrufer.ch/snipsnap/space/Virtual+Brain/Java/Debug-Appender/log4jextension.zip'
                                         s:fileName='log4jextension.zip'
                                         s:contentType='application/zip'
                                         s:size='436343'>
                                        <s:date>Mon Oct 08 14:07:15 CEST 2007</s:date>
                                    </s:Attachment>
                                </rdf:li>
                            </rdf:Bag>
                        </s:attachments>
                    </s:Snip>
                </rdf:li>
                <rdf:li rdf:resource='http://www.stefanrufer.ch/snipsnap/rdf#Virtual+Brain/Java'/>
                <rdf:li rdf:resource='http://www.stefanrufer.ch/snipsnap/rdf#Virtual+Brain'/>
            </rdf:Bag>
        </s:snipLinks>
    </s:Snip>
</rdf:RDF>

