Author: rhauch
Date: 2008-04-21 22:47:53 -0400 (Mon, 21 Apr 2008)
New Revision: 96
Added:
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problem.java
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
trunk/dna-repository/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChange.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChangeListener.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChanges.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/ObservationService.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/InvalidRuleSetException.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleService.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSet.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSetRepositoryMonitor.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/InvalidSequencerPathExpression.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/Sequencer.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerConfig.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerException.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerOutputMap.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerPathExpression.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencingService.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapter.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AbstractServiceAdministrator.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AdministeredService.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/ServiceAdministrator.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/ExecutionContext.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JcrTools.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JndiSessionFactory.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/RepositoryNodePath.java
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/SessionFactory.java
trunk/dna-repository/src/main/resources/org/jboss/dna/repository/
trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties
trunk/dna-repository/src/test/java/org/jboss/dna/repository/
trunk/dna-repository/src/test/java/org/jboss/dna/repository/observation/
trunk/dna-repository/src/test/java/org/jboss/dna/repository/observation/NodeChangeTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleInput.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleResult.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleServiceTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleSetTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerA.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerB.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerConfigTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerPathExpressionTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencingServiceTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapterTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/JndiSessionFactoryTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/SimpleExecutionContext.java
trunk/dna-spi/
trunk/dna-spi/.classpath
trunk/dna-spi/.project
trunk/dna-spi/pom.xml
trunk/dna-spi/src/
trunk/dna-spi/src/main/
trunk/dna-spi/src/main/java/
trunk/dna-spi/src/main/java/org/
trunk/dna-spi/src/main/java/org/jboss/
trunk/dna-spi/src/main/java/org/jboss/dna/
trunk/dna-spi/src/main/java/org/jboss/dna/spi/
trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/
trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/SequencerOutput.java
trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/StreamSequencer.java
trunk/dna-spi/src/main/resources/
trunk/dna-spi/src/test/
trunk/dna-spi/src/test/java/
trunk/dna-spi/src/test/java/org/
trunk/dna-spi/src/test/java/org/jboss/
trunk/dna-spi/src/test/java/org/jboss/dna/
trunk/dna-spi/src/test/java/org/jboss/dna/spi/
trunk/dna-spi/src/test/java/org/jboss/dna/spi/sequencers/
trunk/dna-spi/src/test/resources/
trunk/dna-spi/src/test/resources/log4j.properties
Removed:
trunk/dna-repository/src/main/java/org/jboss/dna/services/AbstractServiceAdministrator.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/AdministeredService.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/ExecutionContext.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/JndiSessionFactory.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/RepositoryNodePath.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/ServiceAdministrator.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/ServicesI18n.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/SessionFactory.java
trunk/dna-repository/src/main/java/org/jboss/dna/services/observation/
trunk/dna-repository/src/main/java/org/jboss/dna/services/rules/
trunk/dna-repository/src/main/java/org/jboss/dna/services/sequencers/
trunk/dna-repository/src/main/java/org/jboss/dna/services/util/
trunk/dna-repository/src/main/resources/org/jboss/dna/services/
trunk/dna-repository/src/test/java/org/jboss/dna/services/JndiSessionFactoryTest.java
trunk/dna-repository/src/test/java/org/jboss/dna/services/SimpleExecutionContext.java
trunk/dna-repository/src/test/java/org/jboss/dna/services/observation/
trunk/dna-repository/src/test/java/org/jboss/dna/services/rules/
trunk/dna-repository/src/test/java/org/jboss/dna/services/sequencers/
trunk/dna-services/
Modified:
trunk/
trunk/dna-integration-tests/pom.xml
trunk/dna-repository/.project
trunk/dna-repository/pom.xml
trunk/dna-repository/src/test/resources/log4j.properties
trunk/dna-repository/src/test/resources/rule_test.dslr
trunk/pom.xml
trunk/sequencers/dna-sequencer-images/pom.xml
trunk/sequencers/dna-sequencer-images/src/main/java/org/jboss/dna/sequencer/images/ImageMetadataSequencer.java
Log:
DNA-57: Refactor codebase to clearly and cleanly separate SPI and API
Property changes on: trunk
___________________________________________________________________
Name: svn:ignore
+ target
Added: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problem.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problem.java
(rev 0)
+++ trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problem.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,120 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.common.collection;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.ArgCheck;
+
+/**
+ * @author Randall Hauch
+ */
+@Immutable
+public class Problem {
+
+ public static final int DEFAULT_CODE = 0;
+
+ public enum Status {
+ ERROR,
+ WARNING,
+ INFO;
+ }
+
+ private final Status status;
+ private final I18n message;
+ private final Object[] parameters;
+ private final Throwable throwable;
+ private final int code;
+ private final String resource;
+ private final String location;
+
+ public Problem( Status status, int code, I18n message, Object... params ) {
+ this(status, code, message, params, null, null, null);
+ }
+
+ public Problem( Status status, int code, I18n message, Object[] params, String
resource, String location, Throwable throwable ) {
+ ArgCheck.isNotNull(status, "status");
+ ArgCheck.isNotNull(message, "message");
+ this.status = status;
+ this.code = code;
+ this.message = message;
+ this.parameters = params;
+ this.resource = resource != null ? resource.trim() : null;
+ this.location = location != null ? location.trim() : null;
+ this.throwable = throwable;
+ }
+
+ /**
+ * @return code
+ */
+ public int getCode() {
+ return this.code;
+ }
+
+ /**
+ * @return location
+ */
+ public String getLocation() {
+ return this.location;
+ }
+
+ /**
+ * Get the message written in the current locale.
+ * @return the message
+ */
+ public String getMessageString() {
+ return this.message.text(this.parameters);
+ }
+
+ /**
+ * @return message
+ */
+ public I18n getMessage() {
+ return this.message;
+ }
+
+ public Object[] getParameters() {
+ return this.parameters;
+ }
+
+ /**
+ * @return resource
+ */
+ public String getResource() {
+ return this.resource;
+ }
+
+ /**
+ * @return status
+ */
+ public Status getStatus() {
+ return this.status;
+ }
+
+ /**
+ * @return throwable
+ */
+ public Throwable getThrowable() {
+ return this.throwable;
+ }
+
+}
Added: trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
===================================================================
--- trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java
(rev 0)
+++
trunk/dna-common/src/main/java/org/jboss/dna/common/collection/Problems.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,187 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.common.collection;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.i18n.I18n;
+
+/**
+ * @author Randall Hauch
+ */
+@NotThreadSafe
+public class Problems implements Iterable<Problem> {
+
+ private List<Problem> problems;
+
+ public Problems() {
+ }
+
+ public void addError( I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message,
params));
+ }
+
+ public void addError( Throwable throwable, I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message, null,
null, throwable));
+ }
+
+ public void addError( I18n message, String resource, String location, Object...
params ) {
+ addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message,
resource, location));
+ }
+
+ public void addError( Throwable throwable, I18n message, String resource, String
location, Object... params ) {
+ addProblem(new Problem(Problem.Status.ERROR, Problem.DEFAULT_CODE, message,
resource, location, throwable));
+ }
+
+ public void addError( int code, I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.ERROR, code, message));
+ }
+
+ public void addError( Throwable throwable, int code, I18n message, Object... params )
{
+ addProblem(new Problem(Problem.Status.ERROR, code, message, null, null,
throwable));
+ }
+
+ public void addError( int code, I18n message, String resource, String location,
Object... params ) {
+ addProblem(new Problem(Problem.Status.ERROR, code, message, resource,
location));
+ }
+
+ public void addError( Throwable throwable, int code, I18n message, String resource,
String location, Object... params ) {
+ addProblem(new Problem(Problem.Status.ERROR, code, message, resource, location,
throwable));
+ }
+
+ public void addWarning( I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message));
+ }
+
+ public void addWarning( Throwable throwable, I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message,
null, null, throwable));
+ }
+
+ public void addWarning( I18n message, String resource, String location, Object...
params ) {
+ addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message,
resource, location));
+ }
+
+ public void addWarning( Throwable throwable, I18n message, String resource, String
location, Object... params ) {
+ addProblem(new Problem(Problem.Status.WARNING, Problem.DEFAULT_CODE, message,
resource, location, throwable));
+ }
+
+ public void addWarning( int code, I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.WARNING, code, message));
+ }
+
+ public void addWarning( Throwable throwable, int code, I18n message, Object... params
) {
+ addProblem(new Problem(Problem.Status.WARNING, code, message, null, null,
throwable));
+ }
+
+ public void addWarning( int code, I18n message, String resource, String location,
Object... params ) {
+ addProblem(new Problem(Problem.Status.WARNING, code, message, resource,
location));
+ }
+
+ public void addWarning( Throwable throwable, int code, I18n message, String resource,
String location, Object... params ) {
+ addProblem(new Problem(Problem.Status.WARNING, code, message, resource, location,
throwable));
+ }
+
+ public void addInfo( I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message));
+ }
+
+ public void addInfo( Throwable throwable, I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message, null,
null, throwable));
+ }
+
+ public void addInfo( I18n message, String resource, String location, Object... params
) {
+ addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message,
resource, location));
+ }
+
+ public void addInfo( Throwable throwable, I18n message, String resource, String
location, Object... params ) {
+ addProblem(new Problem(Problem.Status.INFO, Problem.DEFAULT_CODE, message,
resource, location, throwable));
+ }
+
+ public void addInfo( int code, I18n message, Object... params ) {
+ addProblem(new Problem(Problem.Status.INFO, code, message));
+ }
+
+ public void addInfo( Throwable throwable, int code, I18n message, Object... params )
{
+ addProblem(new Problem(Problem.Status.INFO, code, message, null, null,
throwable));
+ }
+
+ public void addInfo( int code, I18n message, String resource, String location,
Object... params ) {
+ addProblem(new Problem(Problem.Status.INFO, code, message, resource, location));
+ }
+
+ public void addInfo( Throwable throwable, int code, I18n message, String resource,
String location, Object... params ) {
+ addProblem(new Problem(Problem.Status.INFO, code, message, resource, location,
throwable));
+ }
+
+ public boolean hasProblems() {
+ return this.problems != null && this.problems.size() > 0;
+ }
+
+ public boolean hasErrors() {
+ if (this.problems == null) return false;
+ for (Problem problem : this.problems) {
+ if (problem.getStatus() == Problem.Status.ERROR) return true;
+ }
+ return false;
+ }
+
+ public boolean hasWarnings() {
+ if (this.problems == null) return false;
+ for (Problem problem : this.problems) {
+ if (problem.getStatus() == Problem.Status.WARNING) return true;
+ }
+ return false;
+ }
+
+ public boolean hasInfo() {
+ if (this.problems == null) return false;
+ for (Problem problem : this.problems) {
+ if (problem.getStatus() == Problem.Status.INFO) return true;
+ }
+ return false;
+ }
+
+ public boolean isEmpty() {
+ return this.problems == null || this.problems.isEmpty();
+ }
+
+ public int size() {
+ if (this.problems == null) return 0;
+ return this.problems.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<Problem> iterator() {
+ return problems.iterator();
+ }
+
+ protected void addProblem( Problem problem ) {
+ if (problem == null) return;
+ if (problems == null) problems = new LinkedList<Problem>();
+ problems.add(problem);
+ }
+
+}
Modified: trunk/dna-integration-tests/pom.xml
===================================================================
--- trunk/dna-integration-tests/pom.xml 2008-04-22 01:42:33 UTC (rev 95)
+++ trunk/dna-integration-tests/pom.xml 2008-04-22 02:47:53 UTC (rev 96)
@@ -31,7 +31,7 @@
</dependency>
<dependency>
<groupId>org.jboss.dna</groupId>
- <artifactId>dna-services</artifactId>
+ <artifactId>dna-repository</artifactId>
<version>0.1-SNAPSHOT</version>
</dependency>
<dependency>
Copied: trunk/dna-repository (from rev 95, trunk/dna-services)
Modified: trunk/dna-repository/.project
===================================================================
--- trunk/dna-services/.project 2008-04-22 01:42:33 UTC (rev 95)
+++ trunk/dna-repository/.project 2008-04-22 02:47:53 UTC (rev 96)
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
- <name>dna-services</name>
+ <name>dna-repository</name>
<comment></comment>
<projects>
</projects>
Modified: trunk/dna-repository/pom.xml
===================================================================
--- trunk/dna-services/pom.xml 2008-04-22 01:42:33 UTC (rev 95)
+++ trunk/dna-repository/pom.xml 2008-04-22 02:47:53 UTC (rev 96)
@@ -6,10 +6,10 @@
<version>0.1-SNAPSHOT</version>
</parent>
<!-- The groupId and version values are inherited from parent -->
- <artifactId>dna-services</artifactId>
+ <artifactId>dna-repository</artifactId>
<packaging>jar</packaging>
- <name>JBoss DNA Services</name>
- <description>JBoss DNA Engine library with main service
components</description>
+ <name>JBoss DNA Repository</name>
+ <description>JBoss DNA Repository library</description>
<
url>http://labs.jboss.org/dna</url>
<!--
Define the dependencies. Note that all version and scopes default to those
@@ -33,6 +33,11 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-spi</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
<!--
Rules
-->
@@ -43,10 +48,12 @@
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-jsr94</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
+ <scope>test</scope>
</dependency>
<!--
Testing (note the scope)
@@ -54,14 +61,17 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.jmock</groupId>
<artifactId>jmock-junit4</artifactId>
+ <scope>test</scope>
</dependency>
<!--
Logging (require SLF4J API for compiling, but use Log4J and its SLF4J binding for
testing)
@@ -73,10 +83,12 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
+ <scope>test</scope>
</dependency>
<!--
Java Concurrency in Practice annotations
@@ -98,10 +110,12 @@
<dependency>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>jackrabbit-api</artifactId>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.jackrabbit</groupId>
<artifactId>jackrabbit-core</artifactId>
+ <scope>test</scope>
</dependency>
</dependencies>
</project>
Added: trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,101 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository;
+
+import org.jboss.dna.common.i18n.I18n;
+
+/**
+ * @author Randall Hauch
+ */
+public final class RepositoryI18n {
+
+ static {
+ try {
+ I18n.initialize(RepositoryI18n.class);
+ } catch (final Exception err) {
+ System.err.println(err);
+ }
+ }
+
+ public static I18n invalidStateString;
+ public static I18n serviceShutdowAndMayNotBeStarted;
+ public static I18n serviceShutdowAndMayNotBePaused;
+ public static I18n unableToFindRepositoryInJndi;
+ public static I18n errorProcessingEvents;
+ public static I18n errorFindingPropertyNameInPropertyAddedEvent;
+ public static I18n errorFindingPropertyNameInPropertyChangedEvent;
+ public static I18n errorFindingPropertyNameInPropertyRemovedEvent;
+
+ public static I18n unableToObtainJsr94RuleAdministrator;
+ public static I18n errorUsingJsr94RuleAdministrator;
+ public static I18n unableToObtainJsr94ServiceProvider;
+ public static I18n errorAddingOrUpdatingRuleSet;
+ public static I18n errorRollingBackRuleSetAfterUpdateFailed;
+ public static I18n errorReadingRulesAndProperties;
+ public static I18n errorDeregisteringRuleSetBeforeUpdatingIt;
+ public static I18n errorRecreatingRuleSet;
+ public static I18n errorRemovingRuleSet;
+ public static I18n errorRemovingRuleSetUponShutdown;
+ public static I18n unableToFindRuleSet;
+ public static I18n errorExecutingRuleSetWithGlobalsAndFacts;
+ public static I18n unableToBuildRuleSetRegularExpressionPattern;
+
+ public static I18n errorObtainingSessionToRepositoryWorkspace;
+ public static I18n errorWritingProblemsOnRuleSet;
+
+ public static I18n sequencingServiceName;
+ public static I18n unableToChangeExecutionContextWhileRunning;
+ public static I18n unableToStartSequencingServiceWithoutExecutionContext;
+ public static I18n errorWhileSequencingNode;
+ public static I18n errorInRepositoryWhileSequencingNode;
+ public static I18n errorFindingSequencersToRunAgainstNode;
+ public static I18n errorInRepositoryWhileFindingSequencersToRunAgainstNode;
+ public static I18n executionContextHasBeenClosed;
+ public static I18n sequencerTask;
+ public static I18n sequencerSubtask;
+ public static I18n unableToFindPropertyForSequencing;
+ public static I18n sequencingPropertyOnNode;
+ public static I18n writingOutputSequencedFromPropertyOnNodes;
+
+ public static I18n errorReadingPropertiesFromContainerNode;
+ public static I18n requiredPropertyOnNodeWasExpectedToBeStringValue;
+ public static I18n optionalPropertyOnNodeWasExpectedToBeStringValue;
+ public static I18n requiredPropertyOnNodeWasExpectedToBeStringArrayValue;
+ public static I18n optionalPropertyOnNodeWasExpectedToBeStringArrayValue;
+ public static I18n requiredPropertyOnNodeCouldNotBeRead;
+ public static I18n optionalPropertyOnNodeCouldNotBeRead;
+ public static I18n requiredPropertyIsMissingFromNode;
+ public static I18n errorGettingRequiredPropertyFromNode;
+ public static I18n errorGettingOptionalPropertyFromNode;
+ public static I18n errorClosingBinaryStreamForPropertyFromNode;
+ public static I18n requiredNodeDoesNotExistRelativeToNode;
+ public static I18n errorGettingNodeRelativeToNode;
+ public static I18n unknownPropertyValueType;
+
+ public static I18n pathExpressionIsInvalid;
+ public static I18n pathExpressionMayNotBeBlank;
+ public static I18n pathExpressionHasInvalidSelect;
+ public static I18n pathExpressionHasInvalidMatch;
+
+ public static I18n invalidRepositoryNodePath;
+
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/RepositoryI18n.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChange.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChange.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChange.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,176 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.observation;
+
+import java.util.Collections;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.HashCode;
+
+/**
+ * A notification of changes to a node.
+ * @author Randall Hauch
+ */
+@Immutable
+public class NodeChange {
+
+ private final String repositoryWorkspaceName;
+ private final String absolutePath;
+ private final int eventTypes;
+ private final Set<String> modifiedProperties;
+ private final Set<String> removedProperties;
+ private final int hc;
+
+ public NodeChange( String repositoryWorkspaceName, String absolutePath, int
eventTypes, Set<String> modifiedProperties, Set<String> removedProperties ) {
+ assert repositoryWorkspaceName != null;
+ assert absolutePath != null;
+ this.repositoryWorkspaceName = repositoryWorkspaceName;
+ this.absolutePath = absolutePath.trim();
+ this.hc = HashCode.compute(this.repositoryWorkspaceName, this.absolutePath);
+ this.eventTypes = eventTypes;
+ if (modifiedProperties == null) modifiedProperties = Collections.emptySet();
+ if (removedProperties == null) removedProperties = Collections.emptySet();
+ this.modifiedProperties = Collections.unmodifiableSet(modifiedProperties);
+ this.removedProperties = Collections.unmodifiableSet(removedProperties);
+ }
+
+ /**
+ * @return absolutePath
+ */
+ public String getAbsolutePath() {
+ return this.absolutePath;
+ }
+
+ /**
+ * @return repositoryWorkspaceName
+ */
+ public String getRepositoryWorkspaceName() {
+ return this.repositoryWorkspaceName;
+ }
+
+ /**
+ * @return modifiedProperties
+ */
+ public Set<String> getModifiedProperties() {
+ return this.modifiedProperties;
+ }
+
+ /**
+ * @return removedProperties
+ */
+ public Set<String> getRemovedProperties() {
+ return this.removedProperties;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return this.hc;
+ }
+
+ public boolean includesAllEventTypes( int... jcrEventTypes ) {
+ for (int jcrEventType : jcrEventTypes) {
+ if ((this.eventTypes & jcrEventType) == 0) return false;
+ }
+ return true;
+ }
+
+ public boolean includesEventTypes( int... jcrEventTypes ) {
+ for (int jcrEventType : jcrEventTypes) {
+ if ((this.eventTypes & jcrEventType) != 0) return true;
+ }
+ return false;
+ }
+
+ public boolean isSameNode( NodeChange that ) {
+ if (that == this) return true;
+ if (this.hc != that.hc) return false;
+ if (!this.repositoryWorkspaceName.equals(that.repositoryWorkspaceName)) return
false;
+ if (!this.absolutePath.equals(that.absolutePath)) return false;
+ return true;
+ }
+
+ /**
+ * Return whether this node change occurs on a node on the supplied path.
+ * @param absolutePath the path
+ * @return true if the node is on the supplied absolute path, or false otherwise
+ * @see #isNotOnPath(String)
+ */
+ public boolean isOnPath( String absolutePath ) {
+ if (absolutePath == null) return false;
+ if (this.getAbsolutePath().startsWith(absolutePath)) return true;
+ return false;
+ }
+
+ /**
+ * Return whether this node change occurs on a node on a different path than that
supplied.
+ * @param absolutePath the path
+ * @return true if the node is on a different path, or false if it is on the same
path
+ * @see #isOnPath(String)
+ */
+ public boolean isNotOnPath( String absolutePath ) {
+ return !isOnPath(absolutePath);
+ }
+
+ /**
+ * Determine whether this node change includes the setting of new value(s) for the
supplied property.
+ * @param property the name of the property
+ * @return true if the named property has a new value on this node, or false
otherwise
+ */
+ public boolean isPropertyModified( String property ) {
+ return this.modifiedProperties.contains(property);
+ }
+
+ /**
+ * Determine whether this node change includes the removal of the supplied property.
+ * @param property the name of the property
+ * @return true if the named property was removed from this node, or false otherwise
+ */
+ public boolean isPropertyRemoved( String property ) {
+ return this.removedProperties.contains(property);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof NodeChange) {
+ NodeChange that = (NodeChange)obj;
+ if (!this.isSameNode(that)) return false;
+ if (this.eventTypes != that.eventTypes) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return this.repositoryWorkspaceName + "=>" + this.absolutePath;
+ }
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChangeListener.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChangeListener.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChangeListener.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,31 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.observation;
+
+/**
+ * @author Randall Hauch
+ */
+public interface NodeChangeListener {
+
+ void onNodeChanges( NodeChanges changes );
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChanges.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChanges.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/NodeChanges.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,195 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.observation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+import org.jboss.dna.common.i18n.I18n;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.repository.RepositoryI18n;
+
+/**
+ * A utility class that builds node changes from a sequence of events.
+ * @author Randall Hauch
+ */
+public class NodeChanges implements Iterable<NodeChange> {
+
+ public static NodeChanges create( final String repositoryWorkspaceName,
Iterable<Event> events ) throws RepositoryException {
+ Map<String, NodeChangeDetails> detailsByNodePath = new HashMap<String,
NodeChangeDetails>();
+ // Process each of the events, extracting the node path and property details for
each ...
+ for (Event event : events) {
+ final int eventType = event.getType();
+ final String eventPath = event.getPath();
+ if (eventType == Event.PROPERTY_ADDED || eventType == Event.PROPERTY_CHANGED
|| eventType == Event.PROPERTY_REMOVED) {
+ // Extract the node's path and property name from the even path ...
+ int lastDelim = eventPath.lastIndexOf('/');
+ if (lastDelim < 1 || lastDelim == (eventPath.length() - 1)) {
+ // The last delimiter doesn't exist, is the first character, or
is the last character...
+ I18n msg =
+ eventType == Event.PROPERTY_ADDED ?
RepositoryI18n.errorFindingPropertyNameInPropertyAddedEvent : eventType ==
Event.PROPERTY_CHANGED ? RepositoryI18n.errorFindingPropertyNameInPropertyChangedEvent :
RepositoryI18n.errorFindingPropertyNameInPropertyRemovedEvent;
+ Logger.getLogger(NodeChanges.class).error(msg, eventPath);
+ continue;
+ }
+ String nodePath = eventPath.substring(0, lastDelim); // excludes the last
delim
+ String propertyName = eventPath.substring(lastDelim + 1);
+ // Record the details ...
+ NodeChangeDetails details = detailsByNodePath.get(nodePath);
+ if (details == null) {
+ details = new NodeChangeDetails(nodePath);
+ detailsByNodePath.put(nodePath, details);
+ }
+ switch (eventType) {
+ case Event.PROPERTY_ADDED: {
+ details.addProperty(propertyName);
+ break;
+ }
+ case Event.PROPERTY_CHANGED: {
+ details.changeProperty(propertyName);
+ break;
+ }
+ case Event.PROPERTY_REMOVED: {
+ details.removeProperty(propertyName);
+ break;
+ }
+ }
+ } else if (eventType == Event.NODE_ADDED || eventType == Event.NODE_REMOVED)
{
+ // Remove the last delimiter if it appears at the end of the path ...
+ String nodePath = eventPath;
+ if (nodePath.length() > 1 && nodePath.charAt(nodePath.length()
- 1) == '/') {
+ nodePath = nodePath.substring(0, nodePath.length() - 1);
+ }
+ // Record the details ...
+ NodeChangeDetails details = detailsByNodePath.get(nodePath);
+ if (details == null) {
+ details = new NodeChangeDetails(nodePath);
+ detailsByNodePath.put(nodePath, details);
+ }
+ details.addEventType(eventType);
+ }
+ }
+
+ // Create the node changes ...
+ List<NodeChange> result = new
ArrayList<NodeChange>(detailsByNodePath.size());
+ for (NodeChangeDetails detail : detailsByNodePath.values()) {
+ NodeChange change = new NodeChange(repositoryWorkspaceName,
detail.getNodePath(), detail.getEventTypes(), detail.getModifiedProperties(),
detail.getRemovedProperties());
+ result.add(change);
+ }
+ return new NodeChanges(result);
+ }
+
+ protected static class NodeChangeDetails {
+
+ private final String nodePath;
+ private final Set<String> modifiedProperties = new
HashSet<String>();
+ private final Set<String> removedProperties = new HashSet<String>();
+ private int eventTypes;
+
+ protected NodeChangeDetails( String nodePath ) {
+ this.nodePath = nodePath;
+ }
+
+ public void addEventType( int eventType ) {
+ this.eventTypes |= eventType;
+ }
+
+ public void addProperty( String propertyName ) {
+ this.modifiedProperties.add(propertyName);
+ this.eventTypes |= Event.PROPERTY_ADDED;
+ }
+
+ public void changeProperty( String propertyName ) {
+ this.modifiedProperties.add(propertyName);
+ this.eventTypes |= Event.PROPERTY_CHANGED;
+ }
+
+ public void removeProperty( String propertyName ) {
+ this.removedProperties.add(propertyName);
+ this.eventTypes |= Event.PROPERTY_REMOVED;
+ }
+
+ /**
+ * @return nodeAction
+ */
+ public int getEventTypes() {
+ return this.eventTypes;
+ }
+
+ /**
+ * @return nodePath
+ */
+ public String getNodePath() {
+ return this.nodePath;
+ }
+
+ /**
+ * @return addedProperties
+ */
+ public Set<String> getModifiedProperties() {
+ return this.modifiedProperties;
+ }
+
+ /**
+ * @return removedProperties
+ */
+ public Set<String> getRemovedProperties() {
+ return this.removedProperties;
+ }
+ }
+
+ protected static final Comparator<NodeChange> PRE_ORDER = new
Comparator<NodeChange>() {
+
+ public int compare( NodeChange change1, NodeChange change2 ) {
+ return change1.getAbsolutePath().compareTo(change2.getAbsolutePath());
+ }
+ };
+
+ private final List<NodeChange> changesInPreOrder;
+
+ protected NodeChanges( List<NodeChange> changes ) {
+ this.changesInPreOrder = Collections.unmodifiableList(changes);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Iterator<NodeChange> iterator() {
+ return this.changesInPreOrder.iterator();
+ }
+
+ public Iterator<NodeChange> getPreOrder() {
+ return this.changesInPreOrder.iterator();
+ }
+
+ public int size() {
+ return this.changesInPreOrder.size();
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/ObservationService.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/ObservationService.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/observation/ObservationService.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,722 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.observation;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.UnsupportedRepositoryOperationException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.repository.RepositoryI18n;
+import org.jboss.dna.repository.services.AbstractServiceAdministrator;
+import org.jboss.dna.repository.services.AdministeredService;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+import org.jboss.dna.repository.util.SessionFactory;
+
+/**
+ * @author Randall Hauch
+ */
+public class ObservationService implements AdministeredService {
+
+ /**
+ * Interface to which problems with particular events are logged.
+ * @author Randall Hauch
+ */
+ public static interface ProblemLog {
+
+ void error( String repositoryWorkspaceName, Throwable t );
+ }
+
+ /**
+ * Problem log implementation that records problems in the log.
+ * @author Randall Hauch
+ */
+ public class DefaultProblemLog implements ProblemLog {
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error( String repositoryWorkspaceName, Throwable t ) {
+ getLogger().error(t, RepositoryI18n.errorProcessingEvents,
repositoryWorkspaceName);
+ }
+ }
+
+ protected static class NoOpProblemLog implements ProblemLog {
+
+ /**
+ * {@inheritDoc}
+ */
+ public void error( String repositoryWorkspaceName, Throwable t ) {
+ }
+ }
+
+ public static final ProblemLog NO_OP_PROBLEM_LOG = new NoOpProblemLog();
+
+ /**
+ * The administrative component for this service.
+ * @author Randall Hauch
+ */
+ protected class Administrator extends AbstractServiceAdministrator {
+
+ protected Administrator() {
+ super(State.STARTED);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String serviceName() {
+ return "ObservationService";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean awaitTermination( long timeout, TimeUnit unit ) {
+ return true;
+ }
+
+ }
+
+ private Logger logger = Logger.getLogger(this.getClass());
+ private ProblemLog problemLog = new DefaultProblemLog();
+ private final Statistics statistics = new Statistics();
+ private final SessionFactory sessionFactory;
+ private final CopyOnWriteArrayList<EventListener> eventListeners = new
CopyOnWriteArrayList<EventListener>();
+ private final CopyOnWriteArrayList<NodeChangeListener> nodeChangeListeners =
new CopyOnWriteArrayList<NodeChangeListener>();
+ private final Administrator administrator = new Administrator();
+
+ public ObservationService( SessionFactory sessionFactory ) {
+ ArgCheck.isNotNull(sessionFactory, "session factory");
+ this.sessionFactory = sessionFactory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ServiceAdministrator getAdministrator() {
+ return this.administrator;
+ }
+
+ /**
+ * @return sessionFactory
+ */
+ public SessionFactory getSessionFactory() {
+ return this.sessionFactory;
+ }
+
+ /**
+ * Get the statistics for this system.
+ * @return the statistics, which are updated as the system is used
+ */
+ public Statistics getStatistics() {
+ return this.statistics;
+ }
+
+ /**
+ * Get the logger for this system
+ * @return the logger
+ */
+ public Logger getLogger() {
+ return this.logger;
+ }
+
+ /**
+ * Set the logger for this system.
+ * @param logger the logger, or null if the standard logging should be used
+ */
+ public void setLogger( Logger logger ) {
+ this.logger = logger != null ? logger : Logger.getLogger(this.getClass());
+ }
+
+ /**
+ * @return problemLog
+ */
+ public ProblemLog getProblemLog() {
+ return this.problemLog;
+ }
+
+ /**
+ * Set the problem log that will be notified of problems handling events. By default,
such problems are sent to the log.
+ * @param problemLog the new problem log implementation; if null, then the default
problem log is used
+ */
+ public void setProblemLog( ProblemLog problemLog ) {
+ this.problemLog = problemLog != null ? problemLog : new DefaultProblemLog();
+ }
+
+ public boolean addListener( EventListener listener ) {
+ if (listener == null) return false;
+ return this.eventListeners.addIfAbsent(listener);
+ }
+
+ public boolean removeListener( EventListener listener ) {
+ if (listener == null) return false;
+ return this.eventListeners.remove(listener);
+ }
+
+ public boolean addListener( NodeChangeListener listener ) {
+ return this.nodeChangeListeners.addIfAbsent(listener);
+ }
+
+ public boolean removeListener( NodeChangeListener listener ) {
+ if (listener == null) return false;
+ return this.nodeChangeListeners.remove(listener);
+ }
+
+ /**
+ * Monitor the supplied workspace for events of the given type on any node at or
under the supplied path.
+ * <p>
+ * Monitoring is accomplished by registering a listener on the workspace, so this
monitoring only has access to the
+ * information that visible to the session created by the {@link #getSessionFactory()
session factory} for the given
+ * repository and workspace name.
+ * </p>
+ * <p>
+ * The listener returned from this method is not managed by this SequencingService
instance. If the listener is no longer
+ * needed, it simply must be {@link
ObservationManager#removeEventListener(EventListener) removed} as a listener of the
+ * workspace and garbage collected. If this service is {@link
ServiceAdministrator#shutdown() shutdown} while there are still
+ * active listeners, those listeners will disconnect themselves from this service and
the workspace with which they're
+ * registered when they attempt to forward the next events.
+ * </p>
+ * <p>
+ * The set of events that are monitored can be filtered by specifying restrictions
based on characteristics of the node
+ * associated with the event. In the case of event types {@link Event#NODE_ADDED
NODE_ADDED} and
+ * {@link Event#NODE_REMOVED NODE_REMOVED}, the node associated with an event is the
node at (or formerly at) the path
+ * returned by {@link Event#getPath() Event.getPath()}. In the case of event types
+ * {@link Event#PROPERTY_ADDED PROPERTY_ADDED}, {@link Event#PROPERTY_REMOVED
PROPERTY_REMOVED} and
+ * {@link Event#PROPERTY_CHANGED PROPERTY_CHANGED}, the node associated with an event
is the parent node of the property at
+ * (or formerly at) the path returned by <code>Event.getPath</code>:
+ * <ul>
+ * <li> <code>absolutePath</code>, <code>isDeep</code>:
Only events whose associated node is at
+ * <code>absolutePath</code> (or within its subtree, if
<code>isDeep</code> is <code>true</code>) will be received. It
+ * is permissible to register a listener for a path where no node currently exists.
</li>
+ * <li> <code>uuids</code>: Only events whose associated node has
one of the UUIDs in this list will be received. If his
+ * parameter is <code>null</code> then no UUID-related restriction is
placed on events received. </li>
+ * <li> <code>nodeTypeNames</code>: Only events whose associated
node has one of the node types (or a subtype of one of the
+ * node types) in this list will be received. If this parameter is
<code>null</code> then no node type-related restriction
+ * is placed on events received. </li>
+ * </ul>
+ * The restrictions are "ANDed" together. In other words, for a particular
node to be "listened to" it must meet all the
+ * restrictions.
+ * </p>
+ * <p>
+ * Additionally, if <code>noLocal</code> is
<code>true</code>, then events generated by the session through which the
+ * listener was registered are ignored. Otherwise, they are not ignored.
+ * </p>
+ * <p>
+ * The filters of an already-registered {@link WorkspaceListener} can be changed at
runtime by changing the attributes and
+ * {@link WorkspaceListener#reregister() registering}.
+ * </p>
+ * @param repositoryWorkspaceName the name to be used with the session factory to
obtain a session to the repository and
+ * workspace that is to be monitored
+ * @param absolutePath the absolute path of the node at or below which changes are to
be monitored; may be null if all nodes
+ * in the workspace are to be monitored
+ * @param eventTypes the bitmask of the {@link Event} types that are to be monitored
+ * @param isDeep true if events below the node given by the
<code>absolutePath</code> or by the <code>uuids</code> are to
+ * be processed, or false if only the events at the node
+ * @param uuids array of UUIDs of nodes that are to be monitored; may be null or
empty if the UUIDs are not known
+ * @param nodeTypeNames array of node type names that are to be monitored; may be
null or empty if the monitoring has no node
+ * type restrictions
+ * @param noLocal true if the events originating in the supplied workspace are to be
ignored, or false if they are also to be
+ * processed.
+ * @return the listener that was created and registered to perform the monitoring
+ * @throws RepositoryException if there is a problem registering the listener
+ */
+ public WorkspaceListener monitor( String repositoryWorkspaceName, String
absolutePath, int eventTypes, boolean isDeep, String[] uuids, String[] nodeTypeNames,
boolean noLocal )
+ throws RepositoryException {
+ WorkspaceListener listener = new WorkspaceListener(repositoryWorkspaceName,
eventTypes, absolutePath, isDeep, uuids, nodeTypeNames, noLocal);
+ listener.register();
+ return listener;
+ }
+
+ /**
+ * Monitor the supplied workspace for {@link WorkspaceListener#DEFAULT_EVENT_TYPES
default event types} on any node at or
+ * under the supplied path.
+ * <p>
+ * Monitoring is accomplished by registering a listener on the workspace, so this
monitoring only has access to the
+ * information that visible to the session created by the {@link #getSessionFactory()
session factory} for the given
+ * repository and workspace name.
+ * </p>
+ * <p>
+ * The listener returned from this method is not managed by this SequencingService
instance. If the listener is no longer
+ * needed, it simply must be {@link
ObservationManager#removeEventListener(EventListener) removed} as a listener of the
+ * workspace and garbage collected.
+ * </p>
+ * @param repositoryWorkspaceName the name to be used with the session factory to
obtain a session to the repository and
+ * workspace that is to be monitored
+ * @param absolutePath the absolute path of the node at or below which changes are to
be monitored; may be null if all nodes
+ * in the workspace are to be monitored
+ * @param nodeTypeNames the names of the node types that are to be monitored; may be
null or empty if the monitoring has no
+ * node type restrictions
+ * @return the listener that was created and registered to perform the monitoring
+ * @throws RepositoryException if there is a problem registering the listener
+ */
+ public WorkspaceListener monitor( String repositoryWorkspaceName, String
absolutePath, String... nodeTypeNames ) throws RepositoryException {
+ return monitor(repositoryWorkspaceName, absolutePath,
WorkspaceListener.DEFAULT_EVENT_TYPES, WorkspaceListener.DEFAULT_IS_DEEP, null,
nodeTypeNames, WorkspaceListener.DEFAULT_NO_LOCAL);
+ }
+
+ /**
+ * Monitor the supplied workspace for the supplied event types on any node in the
workspace.
+ * <p>
+ * Monitoring is accomplished by registering a listener on the workspace, so this
monitoring only has access to the
+ * information that visible to the session created by the {@link #getSessionFactory()
session factory} for the given
+ * repository and workspace name.
+ * </p>
+ * <p>
+ * The listener returned from this method is not managed by this SequencingService
instance. If the listener is no longer
+ * needed, it simply must be {@link
ObservationManager#removeEventListener(EventListener) removed} as a listener of the
+ * workspace and garbage collected.
+ * </p>
+ * @param repositoryWorkspaceName the name to be used with the session factory to
obtain a session to the repository and
+ * workspace that is to be monitored
+ * @param eventTypes the bitmask of the {@link Event} types that are to be monitored
+ * @param nodeTypeNames the names of the node types that are to be monitored; may be
null or empty if the monitoring has no
+ * node type restrictions
+ * @return the listener that was created and registered to perform the monitoring
+ * @throws RepositoryException if there is a problem registering the listener
+ */
+ public WorkspaceListener monitor( String repositoryWorkspaceName, int eventTypes,
String... nodeTypeNames ) throws RepositoryException {
+ return monitor(repositoryWorkspaceName, WorkspaceListener.DEFAULT_ABSOLUTE_PATH,
eventTypes, WorkspaceListener.DEFAULT_IS_DEEP, null, nodeTypeNames,
WorkspaceListener.DEFAULT_NO_LOCAL);
+ }
+
+ /**
+ * From section 2.8.8 of the JSR-170 specification:
+ * <p>
+ * On each persistent change, those listeners that are entitled to receive one or
more events will have their onEvent method
+ * called and be passed an EventIterator. The EventIterator will contain the event
bundle reflecting the persistent changes
+ * made but excluding those to which that particular listener is not entitled,
according to the listeners access permissions
+ * and filters.
+ * </p>
+ * @param events
+ * @param listener
+ */
+ protected void processEvents( EventIterator eventIterator, WorkspaceListener listener
) {
+ if (eventIterator == null) return;
+ List<Event> events = new ArrayList<Event>();
+ // Copy the events ...
+ while (eventIterator.hasNext()) {
+ events.add((Event)eventIterator.next());
+ }
+ if (!getAdministrator().isStarted()) {
+ this.statistics.recordIgnoredEventSet(events.size());
+ return;
+ }
+
+ // Notify the event listeners ...
+ boolean notifiedSomebody = false;
+ List<EventListener> eventListeners = this.eventListeners; // use one
consistent snapshot
+ if (!eventListeners.isEmpty()) {
+ DelegatingEventIterator eventIter = new
DelegatingEventIterator(events.iterator(), events.size());
+ for (EventListener eventListener : eventListeners) {
+ eventListener.onEvent(eventIter);
+ }
+ notifiedSomebody = true;
+ }
+
+ // Now create the node change events ...
+ List<NodeChangeListener> nodeChangeListeners = this.nodeChangeListeners; //
use one consistent snapshot
+ if (!nodeChangeListeners.isEmpty()) {
+ final String repositoryWorkspaceName =
listener.getRepositoryWorkspaceName();
+ try {
+ NodeChanges nodeChanges = NodeChanges.create(repositoryWorkspaceName,
events);
+
+ // And notify the node change listeners ...
+ int nodeChangeCount = nodeChanges.size();
+ this.statistics.recordNodesChanged(nodeChangeCount);
+ for (NodeChangeListener nodeChangeListener : nodeChangeListeners) {
+ nodeChangeListener.onNodeChanges(nodeChanges);
+ }
+ } catch (Throwable t) {
+ getProblemLog().error(repositoryWorkspaceName, t);
+ }
+ notifiedSomebody = true;
+ }
+
+ if (notifiedSomebody) {
+ this.statistics.recordEventSet(events.size());
+ } else {
+ this.statistics.recordIgnoredEventSet(events.size());
+ }
+ }
+
+ protected class DelegatingEventIterator implements EventIterator {
+
+ private final Iterator<Event> events;
+ private final int size;
+ private int position = 0;
+
+ protected DelegatingEventIterator( Iterator<Event> events, int size ) {
+ this.events = events;
+ this.size = size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Event nextEvent() {
+ ++position;
+ return events.next();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long getPosition() {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public long getSize() {
+ return size;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void skip( long skipNum ) {
+ for (int i = 0; i != skipNum; ++i) {
+ next();
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext() {
+ return events.hasNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Object next() {
+ return events.next();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove() {
+ // does nothing
+ }
+
+ }
+
+ /**
+ * Implementation of the {@link EventListener JCR EventListener} interface, returned
by the sequencing system.
+ * @author Randall Hauch
+ */
+ @ThreadSafe
+ public class WorkspaceListener implements EventListener {
+
+ public static final boolean DEFAULT_IS_DEEP = true;
+ public static final boolean DEFAULT_NO_LOCAL = false;
+ public static final int DEFAULT_EVENT_TYPES = Event.NODE_ADDED | /*
Event.NODE_REMOVED| */Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED /*
|Event.PROPERTY_REMOVED */;
+ public static final String DEFAULT_ABSOLUTE_PATH = "/";
+
+ private final String repositoryWorkspaceName;
+ private final Set<String> uuids;
+ private final Set<String> nodeTypeNames;
+ private final int eventTypes;
+ private final String absolutePath;
+ private final boolean deep;
+ private final boolean noLocal;
+ @GuardedBy( "this" )
+ private transient Session session;
+
+ protected WorkspaceListener( String repositoryWorkspaceName, int eventTypes,
String absPath, boolean isDeep, String[] uuids, String[] nodeTypeNames, boolean noLocal )
{
+ this.repositoryWorkspaceName = repositoryWorkspaceName;
+ this.eventTypes = eventTypes;
+ this.deep = isDeep;
+ this.noLocal = noLocal;
+ this.absolutePath = absPath != null && absPath.trim().length() != 0 ?
absPath.trim() : null;
+ // Set the UUIDs ...
+ Set<String> newUuids = new HashSet<String>();
+ if (uuids != null) {
+ for (String uuid : uuids) {
+ if (uuid != null && uuid.trim().length() != 0)
newUuids.add(uuid.trim());
+ }
+ }
+ this.uuids = Collections.unmodifiableSet(newUuids);
+ // Set the node type names
+ Set<String> newNodeTypeNames = new HashSet<String>();
+ if (nodeTypeNames != null) {
+ for (String nodeTypeName : nodeTypeNames) {
+ if (nodeTypeName != null && nodeTypeName.trim().length() !=
0) newNodeTypeNames.add(nodeTypeName.trim());
+ }
+ }
+ this.nodeTypeNames = Collections.unmodifiableSet(newNodeTypeNames);
+ }
+
+ /**
+ * @return repositoryWorkspaceName
+ */
+ public String getRepositoryWorkspaceName() {
+ return this.repositoryWorkspaceName;
+ }
+
+ /**
+ * @return eventTypes
+ */
+ public int getEventTypes() {
+ return this.eventTypes;
+ }
+
+ /**
+ * @return absolutePath
+ */
+ public String getAbsolutePath() {
+ return this.absolutePath;
+ }
+
+ /**
+ * @return deep
+ */
+ public boolean isDeep() {
+ return this.deep;
+ }
+
+ /**
+ * @return noLocal
+ */
+ public boolean isNoLocal() {
+ return this.noLocal;
+ }
+
+ /**
+ * @return uuids
+ */
+ public Set<String> getUuids() {
+ return this.uuids;
+ }
+
+ /**
+ * @return nodeTypeNames
+ */
+ public Set<String> getNodeTypeNames() {
+ return this.nodeTypeNames;
+ }
+
+ public synchronized boolean isRegistered() {
+ if (this.session != null && getAdministrator().isShutdown()) {
+ // This sequencing system has been shutdown, so unregister this listener
+ try {
+ unregister();
+ } catch (RepositoryException re) {
+ String msg = "Error unregistering workspace listener after
sequencing system has been shutdow.";
+ Logger.getLogger(this.getClass()).debug(re, msg);
+ }
+ }
+ return this.session != null;
+ }
+
+ public synchronized WorkspaceListener register() throws
UnsupportedRepositoryOperationException, RepositoryException {
+ if (this.session != null) return this;
+ this.session =
ObservationService.this.getSessionFactory().createSession(this.repositoryWorkspaceName);
+ String[] uuids = this.uuids.isEmpty() ? null : this.uuids.toArray(new
String[this.uuids.size()]);
+ String[] nodeTypeNames = this.nodeTypeNames.isEmpty() ? null :
this.nodeTypeNames.toArray(new String[this.nodeTypeNames.size()]);
+ this.session.getWorkspace().getObservationManager().addEventListener(this,
eventTypes, absolutePath, deep, uuids, nodeTypeNames, noLocal);
+ return this;
+ }
+
+ public synchronized WorkspaceListener unregister() throws
UnsupportedRepositoryOperationException, RepositoryException {
+ if (this.session == null) return this;
+ try {
+
this.session.getWorkspace().getObservationManager().removeEventListener(this);
+ this.session.logout();
+ } finally {
+ this.session = null;
+ }
+ return this;
+ }
+
+ public synchronized WorkspaceListener reregister() throws
UnsupportedRepositoryOperationException, RepositoryException {
+ unregister();
+ register();
+ return this;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onEvent( EventIterator events ) {
+ if (events != null) {
+ if (getAdministrator().isShutdown()) {
+ // This sequencing system has been shutdown, so unregister this
listener
+ try {
+ unregister();
+ } catch (RepositoryException re) {
+ String msg = "Error unregistering workspace listener after
sequencing system has been shutdow.";
+ Logger.getLogger(this.getClass()).debug(re, msg);
+ }
+ } else {
+ ObservationService.this.processEvents(events, this);
+ }
+ }
+ }
+ }
+
+ /**
+ * The statistics for the system. Each sequencing system has an instance of this
class that is updated.
+ * @author Randall Hauch
+ */
+ @ThreadSafe
+ public class Statistics {
+
+ @GuardedBy( "lock" )
+ private long numberOfEventsIgnored;
+ @GuardedBy( "lock" )
+ private long numberOfEventsEnqueued;
+ @GuardedBy( "lock" )
+ private long numberOfEventSetsIgnored;
+ @GuardedBy( "lock" )
+ private long numberOfEventSetsEnqueued;
+ private final AtomicLong numberOfNodeChangesEnqueued = new AtomicLong(0);
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ private final AtomicLong startTime;
+
+ protected Statistics() {
+ startTime = new AtomicLong(System.currentTimeMillis());
+ }
+
+ public Statistics reset() {
+ try {
+ lock.writeLock().lock();
+ this.startTime.set(System.currentTimeMillis());
+ this.numberOfEventsIgnored = 0;
+ this.numberOfEventsEnqueued = 0;
+ this.numberOfEventSetsIgnored = 0;
+ this.numberOfEventSetsEnqueued = 0;
+ this.numberOfNodeChangesEnqueued.set(0);
+ } finally {
+ lock.writeLock().unlock();
+ }
+ return this;
+ }
+
+ /**
+ * @return the system time when the statistics were started
+ */
+ public long getStartTime() {
+ return this.startTime.get();
+ }
+
+ /**
+ * @return the number of node changes that were processed
+ */
+ public long getNumberOfNodeChangesEnqueued() {
+ return this.numberOfNodeChangesEnqueued.get();
+ }
+
+ /**
+ * @return the number of events that were ignored because the system was not
running
+ */
+ public long getNumberOfEventsIgnored() {
+ try {
+ lock.readLock().lock();
+ return this.numberOfEventsIgnored;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * @return the number of events that were enqueued for processing
+ */
+ public long getNumberOfEventsEnqueued() {
+ try {
+ lock.readLock().lock();
+ return this.numberOfEventsEnqueued;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * @return the number of event sets (transactions) that were enqueued for
processing
+ */
+ public long getNumberOfEventSetsEnqueued() {
+ try {
+ lock.readLock().lock();
+ return this.numberOfEventSetsEnqueued;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ /**
+ * @return the number of event sets (transactions) that were ignored because the
system was not running
+ */
+ public long getNumberOfEventSetsIgnored() {
+ try {
+ lock.readLock().lock();
+ return this.numberOfEventSetsIgnored;
+ } finally {
+ lock.readLock().unlock();
+ }
+ }
+
+ protected void recordNodesChanged( long changeCount ) {
+ this.numberOfNodeChangesEnqueued.addAndGet(changeCount);
+ }
+
+ protected void recordEventSet( long eventsInSet ) {
+ try {
+ lock.writeLock().lock();
+ this.numberOfEventsEnqueued += eventsInSet;
+ ++this.numberOfEventSetsEnqueued;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+
+ protected void recordIgnoredEventSet( long eventsInSet ) {
+ try {
+ lock.writeLock().lock();
+ this.numberOfEventsIgnored += eventsInSet;
+ this.numberOfEventSetsIgnored += 1;
+ ++this.numberOfEventSetsEnqueued;
+ } finally {
+ lock.writeLock().unlock();
+ }
+ }
+ }
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/InvalidRuleSetException.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/InvalidRuleSetException.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/InvalidRuleSetException.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,60 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+/**
+ * @author Randall Hauch
+ */
+public class InvalidRuleSetException extends RuntimeException {
+
+ /**
+ *
+ */
+ public InvalidRuleSetException() {
+ }
+
+ /**
+ * @param message
+ */
+ public InvalidRuleSetException( String message ) {
+ super(message);
+
+ }
+
+ /**
+ * @param cause
+ */
+ public InvalidRuleSetException( Throwable cause ) {
+ super(cause);
+
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public InvalidRuleSetException( String message, Throwable cause ) {
+ super(message, cause);
+
+ }
+
+}
Added: trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleService.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleService.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleService.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,468 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.rmi.RemoteException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import javax.rules.ConfigurationException;
+import javax.rules.RuleRuntime;
+import javax.rules.RuleServiceProvider;
+import javax.rules.RuleServiceProviderManager;
+import javax.rules.RuleSession;
+import javax.rules.StatelessRuleSession;
+import javax.rules.admin.LocalRuleExecutionSetProvider;
+import javax.rules.admin.RuleAdministrator;
+import javax.rules.admin.RuleExecutionSet;
+import javax.rules.admin.RuleExecutionSetCreateException;
+import javax.rules.admin.RuleExecutionSetDeregistrationException;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.SystemFailureException;
+import org.jboss.dna.common.component.ClassLoaderFactory;
+import org.jboss.dna.common.component.StandardClassLoaderFactory;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.repository.RepositoryI18n;
+import org.jboss.dna.repository.services.AbstractServiceAdministrator;
+import org.jboss.dna.repository.services.AdministeredService;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+
+/**
+ * A rule service that is capable of executing rule sets using one or more JSR-94 rule
engines. Sets of rules are
+ * {@link #addRuleSet(RuleSet) added}, {@link #updateRuleSet(RuleSet) updated}, and
{@link #removeRuleSet(String) removed}
+ * (usually by some other component), and then these named rule sets can be {@link
#executeRules(String, Map, Object...) run} with
+ * inputs and facts to obtain output.
+ * <p>
+ * This service is thread safe. While multiple rule sets can be safely {@link
#executeRules(String, Map, Object...) executed} at
+ * the same time, all executions will be properly synchronized with methods to {@link
#addRuleSet(RuleSet) add},
+ * {@link #updateRuleSet(RuleSet) update}, and {@link #removeRuleSet(String) remove} rule
sets.
+ * </p>
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public class RuleService implements AdministeredService {
+
+ protected static final ClassLoaderFactory DEFAULT_CLASSLOADER_FACTORY = new
StandardClassLoaderFactory(RuleService.class.getClassLoader());
+
+ /**
+ * The administrative component for this service.
+ * @author Randall Hauch
+ */
+ protected class Administrator extends AbstractServiceAdministrator {
+
+ protected Administrator() {
+ super(State.PAUSED);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String serviceName() {
+ return "RuleService";
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void doShutdown( State fromState ) {
+ super.doShutdown(fromState);
+ // Remove all rule sets ...
+ removeAllRuleSets();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean awaitTermination( long timeout, TimeUnit unit ) throws
InterruptedException {
+ return doAwaitTermination(timeout, unit);
+ }
+
+ }
+
+ private Logger logger;
+ private ClassLoaderFactory classLoaderFactory = DEFAULT_CLASSLOADER_FACTORY;
+ private final Administrator administrator = new Administrator();
+ private final ReadWriteLock lock = new ReentrantReadWriteLock();
+ @GuardedBy( "lock" )
+ private final Map<String, RuleSet> ruleSets = new HashMap<String,
RuleSet>();
+ private final CountDownLatch shutdownLatch = new CountDownLatch(1);
+
+ /**
+ * Create a new rule service, configured with no rule sets. Upon construction, the
system is
+ * {@link ServiceAdministrator#isPaused() paused} and must be configured and then
{@link ServiceAdministrator#start() started}.
+ */
+ public RuleService() {
+ this.logger = Logger.getLogger(this.getClass());
+ }
+
+ /**
+ * Return the administrative component for this service.
+ * @return the administrative component; never null
+ */
+ public ServiceAdministrator getAdministrator() {
+ return this.administrator;
+ }
+
+ /**
+ * Get the class loader factory that should be used to load sequencers. By default,
this service uses a factory that will
+ * return either the {@link Thread#getContextClassLoader() current thread's
context class loader} (if not null) or the class
+ * loader that loaded this class.
+ * @return the class loader factory; never null
+ * @see #setClassLoaderFactory(ClassLoaderFactory)
+ */
+ public ClassLoaderFactory getClassLoaderFactory() {
+ return this.classLoaderFactory;
+ }
+
+ /**
+ * Set the Maven Repository that should be used to load the sequencer classes. By
default, this service uses a class loader
+ * factory that will return either the {@link Thread#getContextClassLoader() current
thread's context class loader} (if not
+ * null) or the class loader that loaded this class.
+ * @param classLoaderFactory the class loader factory reference, or null if the
default class loader factory should be used.
+ * @see #getClassLoaderFactory()
+ */
+ public void setClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) {
+ this.classLoaderFactory = classLoaderFactory != null ? classLoaderFactory :
DEFAULT_CLASSLOADER_FACTORY;
+ }
+
+ /**
+ * Obtain the rule sets that are currently available in this service.
+ * @return an unmodifiable copy of the rule sets; never null, but possibly empty ...
+ */
+ public Collection<RuleSet> getRuleSets() {
+ List<RuleSet> results = new ArrayList<RuleSet>();
+ try {
+ this.lock.readLock().lock();
+ // Make a copy of the rule sets ...
+ if (ruleSets.size() != 0) results.addAll(this.ruleSets.values());
+ } finally {
+ this.lock.readLock().unlock();
+ }
+ return Collections.unmodifiableList(results);
+ }
+
+ /**
+ * Add a rule set, or update any existing one that represents the {@link
RuleSet#equals(Object) same rule set}
+ * @param ruleSet the new rule set
+ * @return true if the rule set was added, or false if the rule set was not added
(because it wasn't necessary)
+ * @throws IllegalArgumentException if <code>ruleSet</code> is null
+ * @throws InvalidRuleSetException if the supplied rule set is invalid, incomplete,
incorrectly defined, or uses a JSR-94
+ * service provider that cannot be found
+ * @see #updateRuleSet(RuleSet)
+ * @see #removeRuleSet(String)
+ */
+ public boolean addRuleSet( RuleSet ruleSet ) {
+ ArgCheck.isNotNull(ruleSet, "rule set");
+ final String providerUri = ruleSet.getProviderUri();
+ final String ruleSetName = ruleSet.getName();
+ final String rules = ruleSet.getRules();
+ final Map<?, ?> properties = ruleSet.getExecutionSetProperties();
+ final Reader ruleReader = new StringReader(rules);
+ boolean updatedRuleSets = false;
+ try {
+ this.lock.writeLock().lock();
+
+ // Make sure the rule service provider is available ...
+ RuleServiceProvider ruleServiceProvider = findRuleServiceProvider(ruleSet);
+ assert ruleServiceProvider != null;
+
+ // Now register a new execution set ...
+ RuleAdministrator ruleAdmin = ruleServiceProvider.getRuleAdministrator();
+ if (ruleAdmin == null) {
+ throw new
InvalidRuleSetException(RepositoryI18n.unableToObtainJsr94RuleAdministrator.text(providerUri,
ruleSet.getComponentClassname(), ruleSetName));
+ }
+
+ // Is there is an existing rule set and, if so, whether it has changed ...
+ RuleSet existing = this.ruleSets.get(ruleSetName);
+
+ // Create the rule execution set (do this before deregistering, in case there
is a problem)...
+ LocalRuleExecutionSetProvider ruleExecutionSetProvider =
ruleAdmin.getLocalRuleExecutionSetProvider(null);
+ RuleExecutionSet executionSet =
ruleExecutionSetProvider.createRuleExecutionSet(ruleReader, properties);
+
+ // We should add the execiting rule set if there wasn't one or if the
rule set has changed ...
+ boolean shouldAdd = existing == null || ruleSet.hasChanged(existing);
+ if (existing != null && shouldAdd) {
+ // There is an existing execution set and it needs to be updated, so
deregister it ...
+ ruleServiceProvider = deregister(ruleSet);
+ }
+ if (shouldAdd) {
+ boolean rollback = false;
+ try {
+ // Now register the new execution set and update the rule set managed
by this service ...
+ ruleAdmin.registerRuleExecutionSet(ruleSetName, executionSet, null);
+ this.ruleSets.remove(ruleSet.getName());
+ this.ruleSets.put(ruleSet.getName(), ruleSet);
+ updatedRuleSets = true;
+ } catch (Throwable t) {
+ rollback = true;
+ throw new
InvalidRuleSetException(RepositoryI18n.errorAddingOrUpdatingRuleSet.text(ruleSet.getName()),
t);
+ } finally {
+ if (rollback) {
+ try {
+ // There was a problem, so re-register the original existing
rule set ...
+ if (existing != null) {
+ final String oldRules = existing.getRules();
+ final Map<?, ?> oldProperties =
existing.getExecutionSetProperties();
+ final Reader oldRuleReader = new StringReader(oldRules);
+ ruleServiceProvider = findRuleServiceProvider(existing);
+ assert ruleServiceProvider != null;
+ executionSet =
ruleExecutionSetProvider.createRuleExecutionSet(oldRuleReader, oldProperties);
+ ruleAdmin.registerRuleExecutionSet(ruleSetName,
executionSet, null);
+ this.ruleSets.remove(ruleSetName);
+ this.ruleSets.put(ruleSetName, existing);
+ }
+ } catch (Throwable rollbackError) {
+ // There was a problem rolling back to the existing rule set,
and we're going to throw the
+ // exception associated with the updated/new rule set, so
just log this problem
+ this.logger.error(rollbackError,
RepositoryI18n.errorRollingBackRuleSetAfterUpdateFailed, ruleSetName);
+ }
+ }
+ }
+ }
+ } catch (InvalidRuleSetException e) {
+ throw e;
+ } catch (ConfigurationException t) {
+ throw new
InvalidRuleSetException(RepositoryI18n.unableToObtainJsr94RuleAdministrator.text(providerUri,
ruleSet.getComponentClassname(), ruleSetName));
+ } catch (RemoteException t) {
+ throw new
InvalidRuleSetException(RepositoryI18n.errorUsingJsr94RuleAdministrator.text(providerUri,
ruleSet.getComponentClassname(), ruleSetName));
+ } catch (IOException t) {
+ throw new
InvalidRuleSetException(RepositoryI18n.errorReadingRulesAndProperties.text(ruleSetName));
+ } catch (RuleExecutionSetDeregistrationException t) {
+ throw new
InvalidRuleSetException(RepositoryI18n.errorDeregisteringRuleSetBeforeUpdatingIt.text(ruleSetName));
+ } catch (RuleExecutionSetCreateException t) {
+ throw new
InvalidRuleSetException(RepositoryI18n.errorRecreatingRuleSet.text(ruleSetName));
+ } finally {
+ this.lock.writeLock().unlock();
+ }
+ return updatedRuleSets;
+ }
+
+ /**
+ * Update the configuration for a sequencer, or add it if there is no {@link
RuleSet#equals(Object) matching configuration}.
+ * @param ruleSet the rule set to be updated
+ * @return true if the rule set was updated, or false if the rule set was not updated
(because it wasn't necessary)
+ * @throws InvalidRuleSetException if the supplied rule set is invalid, incomplete,
incorrectly defined, or uses a JSR-94
+ * service provider that cannot be found
+ * @see #addRuleSet(RuleSet)
+ * @see #removeRuleSet(String)
+ */
+ public boolean updateRuleSet( RuleSet ruleSet ) {
+ return addRuleSet(ruleSet);
+ }
+
+ /**
+ * Remove a rule set.
+ * @param ruleSetName the name of the rule set to be removed
+ * @return true if the rule set was removed, or if it was not an existing rule set
+ * @throws IllegalArgumentException if <code>ruleSetName</code> is null
or empty
+ * @throws SystemFailureException if the rule set was found but there was a problem
removing it
+ * @see #addRuleSet(RuleSet)
+ * @see #updateRuleSet(RuleSet)
+ */
+ public boolean removeRuleSet( String ruleSetName ) {
+ ArgCheck.isNotEmpty(ruleSetName, "rule set");
+ try {
+ this.lock.writeLock().lock();
+ RuleSet ruleSet = this.ruleSets.remove(ruleSetName);
+ if (ruleSet != null) {
+ try {
+ deregister(ruleSet);
+ } catch (Throwable t) {
+ // There was a problem deregistering the rule set, so put it back
...
+ this.ruleSets.put(ruleSetName, ruleSet);
+ }
+ return true;
+ }
+ } catch (Throwable t) {
+ throw new
SystemFailureException(RepositoryI18n.errorRemovingRuleSet.text(ruleSetName), t);
+ } finally {
+ this.lock.writeLock().unlock();
+ }
+ return false;
+ }
+
+ /**
+ * Get the logger for this system
+ * @return the logger
+ */
+ public Logger getLogger() {
+ return this.logger;
+ }
+
+ /**
+ * Set the logger for this system.
+ * @param logger the logger, or null if the standard logging should be used
+ */
+ public void setLogger( Logger logger ) {
+ this.logger = logger != null ? logger : Logger.getLogger(this.getClass());
+ }
+
+ /**
+ * Execute the set of rules defined by the supplied rule set name. This method is
safe to be concurrently called by multiple
+ * threads, and is properly synchronized with the methods to {@link
#addRuleSet(RuleSet) add},
+ * {@link #updateRuleSet(RuleSet) update}, and {@link #removeRuleSet(String) remove}
rule sets.
+ * @param ruleSetName the {@link RuleSet#getName() name} of the {@link RuleSet} that
should be used
+ * @param globals the global variables
+ * @param facts the facts
+ * @return the results of executing the rule set
+ * @throws IllegalArgumentException if the rule set name is null, empty or blank, or
if there is no rule set with the given
+ * name
+ * @throws SystemFailureException if there is no JSR-94 rule service provider with
the
+ * {@link RuleSet#getProviderUri() RuleSet's provider URI}.
+ */
+ public List<?> executeRules( String ruleSetName, Map<String, Object>
globals, Object... facts ) {
+ ArgCheck.isNotEmpty(ruleSetName, "rule set name");
+ List<?> result = null;
+ List<?> factList = Arrays.asList(facts);
+ try {
+ this.lock.readLock().lock();
+
+ // Find the rule set ...
+ RuleSet ruleSet = this.ruleSets.get(ruleSetName);
+ if (ruleSet == null) {
+ throw new
IllegalArgumentException(RepositoryI18n.unableToFindRuleSet.text(ruleSetName));
+ }
+
+ // Look up the provider ...
+ RuleServiceProvider ruleServiceProvider = findRuleServiceProvider(ruleSet);
+ assert ruleServiceProvider != null;
+
+ // Create the rule session ...
+ RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();
+ String executionSetName = ruleSet.getRuleSetUri();
+ RuleSession session = ruleRuntime.createRuleSession(executionSetName,
globals, RuleRuntime.STATELESS_SESSION_TYPE);
+ try {
+ StatelessRuleSession statelessSession = (StatelessRuleSession)session;
+ result = statelessSession.executeRules(factList);
+ } finally {
+ session.release();
+ }
+ if (this.logger.isTraceEnabled()) {
+ String msg = "Executed rule set '{1}' with globals {2} and
facts {3} resulting in {4}";
+ this.logger.trace(msg, ruleSetName, StringUtil.readableString(globals),
StringUtil.readableString(facts), StringUtil.readableString(result));
+ }
+ } catch (Throwable t) {
+ throw new
SystemFailureException(RepositoryI18n.errorExecutingRuleSetWithGlobalsAndFacts.text(ruleSetName,
StringUtil.readableString(globals), StringUtil.readableString(facts)), t);
+ } finally {
+ this.lock.readLock().unlock();
+ }
+ return result;
+ }
+
+ protected void removeAllRuleSets() {
+ try {
+ lock.writeLock().lock();
+ for (RuleSet ruleSet : ruleSets.values()) {
+ try {
+ deregister(ruleSet);
+ } catch (Throwable t) {
+ logger.error(t, RepositoryI18n.errorRemovingRuleSetUponShutdown,
ruleSet.getName());
+ }
+ }
+ } finally {
+ lock.writeLock().unlock();
+ }
+ this.shutdownLatch.countDown();
+ }
+
+ protected boolean doAwaitTermination( long timeout, TimeUnit unit ) throws
InterruptedException {
+ return this.shutdownLatch.await(timeout, unit);
+ }
+
+ /**
+ * Finds the JSR-94 service provider instance and returns it. If it could not be
found, this method attempts to load it.
+ * @param ruleSet the rule set for which the service provider is to be found; may not
be null
+ * @return the rule service provider; never null
+ * @throws ConfigurationException if there is a problem loading the service provider
+ * @throws InvalidRuleSetException if the service provider could not be found
+ */
+ private RuleServiceProvider findRuleServiceProvider( RuleSet ruleSet ) throws
ConfigurationException {
+ assert ruleSet != null;
+ String providerUri = ruleSet.getProviderUri();
+ RuleServiceProvider ruleServiceProvider = null;
+ try {
+ // If the provider could not be found, then a ConfigurationException will be
thrown ...
+ ruleServiceProvider =
RuleServiceProviderManager.getRuleServiceProvider(providerUri);
+ } catch (ConfigurationException e) {
+ try {
+ // Use JSR-94 to load the RuleServiceProvider instance ...
+ ClassLoader loader =
this.classLoaderFactory.getClassLoader(ruleSet.getComponentClasspathArray());
+ // Don't call ClassLoader.loadClass(String), as this doesn't
initialize the class!!
+ Class.forName(ruleSet.getComponentClassname(), true, loader);
+ ruleServiceProvider =
RuleServiceProviderManager.getRuleServiceProvider(providerUri);
+ this.logger.debug("Loaded the rule service provider {0} ({1})",
providerUri, ruleSet.getComponentClassname());
+ } catch (ConfigurationException ce) {
+ throw ce;
+ } catch (Throwable t) {
+ throw new
InvalidRuleSetException(RepositoryI18n.unableToObtainJsr94ServiceProvider.text(providerUri,
ruleSet.getComponentClassname()), t);
+ }
+ }
+ if (ruleServiceProvider == null) {
+ throw new
InvalidRuleSetException(RepositoryI18n.unableToObtainJsr94ServiceProvider.text(providerUri,
ruleSet.getComponentClassname()));
+ }
+ return ruleServiceProvider;
+ }
+
+ /**
+ * Deregister the supplied rule set, if it could be found. This method does nothing
if any of the service provider components
+ * could not be found.
+ * @param ruleSet the rule set to be deregistered; may not be null
+ * @return the service provider reference, or null if the service provider could not
be found ...
+ * @throws ConfigurationException
+ * @throws RuleExecutionSetDeregistrationException
+ * @throws RemoteException
+ */
+ private RuleServiceProvider deregister( RuleSet ruleSet ) throws
ConfigurationException, RuleExecutionSetDeregistrationException, RemoteException {
+ assert ruleSet != null;
+ // Look up the provider ...
+ String providerUri = ruleSet.getProviderUri();
+ assert providerUri != null;
+
+ // Look for the provider ...
+ RuleServiceProvider ruleServiceProvider =
RuleServiceProviderManager.getRuleServiceProvider(providerUri);
+ if (ruleServiceProvider != null) {
+ // Deregister the rule set ...
+ RuleAdministrator ruleAdmin = ruleServiceProvider.getRuleAdministrator();
+ if (ruleAdmin != null) {
+ ruleAdmin.deregisterRuleExecutionSet(ruleSet.getRuleSetUri(), null);
+ }
+ }
+ return ruleServiceProvider;
+ }
+
+}
Added: trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSet.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSet.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSet.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,152 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import javax.rules.RuleServiceProvider;
+import javax.rules.admin.RuleExecutionSet;
+import javax.rules.admin.RuleExecutionSetProvider;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.component.ClassLoaderFactory;
+import org.jboss.dna.common.component.ComponentConfig;
+import org.jboss.dna.common.util.ArgCheck;
+
+/**
+ * A description of a set of rules compatible with a JSR-94 rule engine.
+ * @author Randall Hauch
+ */
+@Immutable
+public class RuleSet extends ComponentConfig implements Cloneable {
+
+ private final String providerUri;
+ private final String ruleSetUri;
+ private final String rules;
+ private final Map<String, Object> properties;
+
+ /**
+ * Create a JSR-94 rule set definition.
+ * @param name the name of the rule set, which is considered the unique identifier
+ * @param description the description
+ * @param classname the name of the Java class used for the component
+ * @param classpath the optional classpath (defined in a way compatible with a {@link
ClassLoaderFactory}
+ * @param providerUri the URI of the JSR-94 {@link RuleServiceProvider}
implementation to use
+ * @param ruleSetUri the URI of the JSR-94 {@link RuleExecutionSet} represented by
this object; if null, the name is used
+ * @param rules the string containing the rules in a provider-specific language
+ * @param properties the provider-specific properties, whose values should be strings
or byte arrays (the latter if the
+ * provider expects an {@link Reader} with the value)
+ * @throws IllegalArgumentException if any of the name, classname, provider URI, or
rules parameters are null, empty or blank,
+ * or if the classname is not a valid Java classname
+ */
+ public RuleSet( String name, String description, String classname, String[]
classpath, String providerUri, String ruleSetUri, String rules, Map<String, Object>
properties ) {
+ super(name, description, System.currentTimeMillis(), classname, classpath);
+ if (ruleSetUri == null) ruleSetUri = name.trim();
+ ArgCheck.isNotEmpty(ruleSetUri, "rule set URI");
+ ArgCheck.isNotEmpty(providerUri, "provider URI");
+ ArgCheck.isNotEmpty(rules, "rules");
+ this.providerUri = providerUri;
+ this.ruleSetUri = ruleSetUri;
+ this.rules = rules;
+ if (properties == null) properties = Collections.emptyMap();
+ this.properties = Collections.unmodifiableMap(properties);
+ }
+
+ /**
+ * Get the URI of the JSR-94 {@link RuleServiceProvider} implementation that should
be used.
+ * @return the URI of the JSR-94 implementation; never null, empty or blank
+ */
+ public String getProviderUri() {
+ return this.providerUri;
+ }
+
+ /**
+ * Get the URI of this rule set. The value must be valid as defined by JSR-94 {@link
RuleExecutionSet}.
+ * @return the rule set's URI; never null, empty or blank
+ */
+ public String getRuleSetUri() {
+ return this.ruleSetUri;
+ }
+
+ /**
+ * Get the rules defined in terms of the language reqired by the {@link
#getProviderUri() provider}.
+ * @return the rules for this rule set
+ */
+ public String getRules() {
+ return this.rules;
+ }
+
+ /**
+ * Get this rule set's properties as an unmodifiable map. Note that the values of
these properties are either strings if the
+ * value is to be {@link #getExecutionSetProperties() passed} literally, or a byte
array if the value is to be
+ * {@link #getExecutionSetProperties() passed} as an InputStream.
+ * @return the unmodifiable properties; never null but possible empty
+ */
+ public Map<String, Object> getProperties() {
+ return this.properties;
+ }
+
+ /**
+ * Get the properties for this rule set that can be passed to an {@link
RuleExecutionSetProvider}'s
+ * {@link RuleExecutionSetProvider#createRuleExecutionSet(String, Map)
createRuleExecutionSet} method.
+ * <p>
+ * This method converts any byte array value in the {@link #getProperties()
properties} into an {@link Reader}. Since
+ * {@link ByteArrayInputStream} is used, there is no need to close these stream.
+ * </p>
+ * @return the properties; never null but possible empty
+ */
+ public Map<Object, Object> getExecutionSetProperties() {
+ Map<Object, Object> props = new HashMap<Object, Object>();
+ for (Map.Entry<String, Object> entry : this.properties.entrySet()) {
+ String key = entry.getKey();
+ Object value = entry.getValue();
+ if (value instanceof byte[]) {
+ value = new InputStreamReader(new ByteArrayInputStream((byte[])value));
+ }
+ props.put(key, value);
+ }
+ return props;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasChanged( ComponentConfig obj ) {
+ if (super.hasChanged(obj)) return true;
+ RuleSet that = (RuleSet)obj;
+ if (!this.providerUri.equals(that.providerUri)) return true;
+ if (!this.ruleSetUri.equals(that.ruleSetUri)) return true;
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public RuleSet clone() {
+ return new RuleSet(this.getName(), this.getDescription(),
this.getComponentClassname(), this.getComponentClasspathArray(), this.providerUri,
this.ruleSetUri, this.rules, this.properties);
+ }
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSetRepositoryMonitor.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSetRepositoryMonitor.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/rules/RuleSetRepositoryMonitor.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,257 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.repository.RepositoryI18n;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.observation.NodeChangeListener;
+import org.jboss.dna.repository.observation.NodeChanges;
+import org.jboss.dna.repository.observation.ObservationService;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.JcrTools;
+
+/**
+ * A component that can listen to a JCR repository and keep the {@link RuleSet} instances
of a {@link RuleService} synchronized
+ * with that repository.
+ * <p>
+ * This class is a {@link NodeChangeListener} that can {@link
ObservationService#addListener(NodeChangeListener) subscribe} to
+ * changes in one or more JCR repositories being monitored by an {@link
ObservationService}. As changes under the rule sets
+ * branch are discovered, they are processed asynchronously. This ensure that the
processing of the repository contents does not
+ * block the other listeners of the {@link ObservationService}.
+ * </p>
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public class RuleSetRepositoryMonitor implements NodeChangeListener {
+
+ public static final String DEFAULT_JCR_ABSOLUTE_PATH =
"/dna:system/dna:ruleSets/";
+
+ protected static final String JCR_PATH_DELIM = "/";
+
+ private final ExecutionContext executionContext;
+ private final RuleService ruleService;
+ private final String jcrAbsolutePath;
+ private final Pattern ruleSetNamePattern;
+ private final ExecutorService executorService;
+ private Logger logger;
+
+ /**
+ * Create an instance that can listen to the {@link RuleSet} definitions stored in a
JCR repository and ensure that the
+ * {@link RuleSet} instances of a {@link RuleService} reflect the definitions in the
repository.
+ * @param ruleService the rule service that should be kept in sync with the JCR
repository.
+ * @param jcrAbsolutePath the absolute path to the branch where the rule sets are
defined; if null or empty, the
+ * {@link #DEFAULT_JCR_ABSOLUTE_PATH default path} is used
+ * @param executionContext the context in which this monitor is to execute
+ * @throws IllegalArgumentException if the rule service or execution context is null,
or if the supplied
+ * <code>jcrAbsolutePath</code> is invalid
+ */
+ public RuleSetRepositoryMonitor( RuleService ruleService, String jcrAbsolutePath,
ExecutionContext executionContext ) {
+ ArgCheck.isNotNull(ruleService, "rule service");
+ ArgCheck.isNotNull(executionContext, "execution context");
+ this.ruleService = ruleService;
+ this.executionContext = executionContext;
+ this.executorService = Executors.newSingleThreadExecutor();
+ this.logger = Logger.getLogger(this.getClass());
+ if (jcrAbsolutePath != null) jcrAbsolutePath = jcrAbsolutePath.trim();
+ this.jcrAbsolutePath = jcrAbsolutePath != null &&
jcrAbsolutePath.length() != 0 ? jcrAbsolutePath : DEFAULT_JCR_ABSOLUTE_PATH;
+ try {
+ // Create the pattern to extract the rule set name from the absolute path
...
+ String leadingPath = this.jcrAbsolutePath;
+ if (!leadingPath.endsWith(JCR_PATH_DELIM)) leadingPath = leadingPath +
JCR_PATH_DELIM;
+ this.ruleSetNamePattern = Pattern.compile(leadingPath +
"([^/]+)/?.*");
+ } catch (PatternSyntaxException e) {
+ throw new
IllegalArgumentException(RepositoryI18n.unableToBuildRuleSetRegularExpressionPattern.text(e.getPattern(),
jcrAbsolutePath, e.getDescription()));
+ }
+ }
+
+ /**
+ * @return ruleService
+ */
+ public RuleService getRuleService() {
+ return this.ruleService;
+ }
+
+ /**
+ * @return jcrAbsolutePath
+ */
+ public String getAbsolutePathToRuleSets() {
+ return this.jcrAbsolutePath;
+ }
+
+ /**
+ * @return logger
+ */
+ public Logger getLogger() {
+ return this.logger;
+ }
+
+ /**
+ * @param logger Sets logger to the specified value.
+ */
+ public void setLogger( Logger logger ) {
+ this.logger = logger;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onNodeChanges( NodeChanges changes ) {
+ final Map<String, Set<String>> ruleSetNamesByWorkspaceName = new
HashMap<String, Set<String>>();
+ for (NodeChange nodeChange : changes) {
+ if (nodeChange.isNotOnPath(this.jcrAbsolutePath)) continue;
+ // Use a regular expression on the absolute path to get the name of the rule
set that is affected ...
+ Matcher matcher =
this.ruleSetNamePattern.matcher(nodeChange.getAbsolutePath());
+ if (!matcher.matches()) continue;
+ String ruleSetName = matcher.group(1);
+ // Record the repository name ...
+ String workspaceName = nodeChange.getRepositoryWorkspaceName();
+ Set<String> ruleSetNames =
ruleSetNamesByWorkspaceName.get(workspaceName);
+ if (ruleSetNames == null) {
+ ruleSetNames = new HashSet<String>();
+ ruleSetNamesByWorkspaceName.put(workspaceName, ruleSetNames);
+ }
+ // Record the rule set name ...
+ ruleSetNames.add(ruleSetName);
+
+ }
+ if (ruleSetNamesByWorkspaceName.isEmpty()) return;
+ // Otherwise there are changes, so submit the names to the executor service ...
+ this.executorService.execute(new Runnable() {
+
+ public void run() {
+ processRuleSets(ruleSetNamesByWorkspaceName);
+ }
+ });
+ }
+
+ /**
+ * Process the rule sets given by the supplied names, keyed by the repository
workspace name.
+ * @param ruleSetNamesByWorkspaceName the set of rule set names keyed by the
repository workspace name
+ */
+ protected void processRuleSets( Map<String, Set<String>>
ruleSetNamesByWorkspaceName ) {
+ final JcrTools tools = this.executionContext.getTools();
+ final String relPathToRuleSets = getAbsolutePathToRuleSets().substring(1);
+ for (Map.Entry<String, Set<String>> entry :
ruleSetNamesByWorkspaceName.entrySet()) {
+ String workspaceName = entry.getKey();
+ Session session = null;
+ try {
+ session =
this.executionContext.getSessionFactory().createSession(workspaceName);
+ // Look up the node that represents the parent of the rule set nodes ...
+ Node ruleSetsNode = session.getRootNode().getNode(relPathToRuleSets);
+
+ for (String ruleSetName : entry.getValue()) {
+ // Look up the node that represents the rule set...
+ if (ruleSetsNode.hasNode(ruleSetName)) {
+ // We don't handle multiple siblings with the same name, so
this should grab the first one ...
+ Node ruleSetNode = ruleSetsNode.getNode(ruleSetName);
+ RuleSet ruleSet = buildRuleSet(ruleSetName, ruleSetNode, tools);
+ if (ruleSet != null) {
+ // Only do something if the RuleSet was instantiated ...
+ getRuleService().addRuleSet(ruleSet);
+ }
+ } else {
+ // The node doesn't exist, so remove the rule set ...
+ getRuleService().removeRuleSet(ruleSetName);
+ }
+ }
+ } catch (RepositoryException e) {
+ getLogger().error(e,
RepositoryI18n.errorObtainingSessionToRepositoryWorkspace, workspaceName);
+ } finally {
+ if (session != null) session.logout();
+ }
+ }
+ }
+
+ /**
+ * Create a rule set from the supplied node. This is called whenever a branch of the
repository is changed.
+ * <p>
+ * This implementation expects a node of type 'dna:ruleSet' and the following
properties (expressed as XPath statements
+ * relative to the supplied node):
+ * <ul>
+ * <li>The {@link RuleSet#getDescription() description} is obtained from the
"<code>./@jcr:description</code>" string
+ * property. This property is optional.</li>
+ * <li>The {@link RuleSet#getComponentClassname() classname} is obtained from
the "<code>./@dna:classname</code>" string
+ * property. This property is required.</li>
+ * <li>The {@link RuleSet#getComponentClasspath() classpath} is obtained from
the "<code>./@dna:classpath</code>" string
+ * property. This property is optional, and if abscent then the classpath will be
assumed from the current context.</li>
+ * <li>The {@link RuleSet#getProviderUri() provider URI} is obtained from the
"<code>./@dna:serviceProviderUri</code>"
+ * string property, and corresponds to the URI of the JSR-94 rules engine service
provider. This property is required.</li>
+ * <li>The {@link RuleSet#getRuleSetUri() rule set URI} is obtained from the
"<code>./@dna:ruleSetUri</code>" string
+ * property. This property is optional and defaults to the node name (e.g.,
"<code>./@jcr:name</code>").</li>
+ * <li>The {@link RuleSet#getRules() definition of the rules} is obtained from
the "<code>./@dna:rules</code>" string
+ * property. This property is required and must be in a form suitable for the JSR-94
rules engine.</li>
+ * <li>The {@link RuleSet#getProperties() properties} are obtained from the
"<code>./dna:properties[contains(@jcr:mixinTypes,'dna:propertyContainer')]/*[@jcr:nodeType='dna:property']</code>"
+ * property nodes, where the name of the property is extracted from the property
node's "<code>./@jcr:name</code>" string
+ * property and the value of the property is extracted from the property node's
"<code>./@dna:propertyValue</code>" string
+ * property. Rule set properties are optional.</li>
+ * </ul>
+ * </p>
+ * @param name the name of the rule set; never null
+ * @param ruleSetNode the node representing the rule set; null if the rule set
doesn't exist
+ * @return the rule set for the information stored in the repository, or null if the
rule set does not exist or has errors
+ */
+ protected RuleSet buildRuleSet( String name, Node ruleSetNode, JcrTools tools ) {
+ if (ruleSetNode == null) return null;
+
+ Problems problems = new Problems();
+ String description = tools.getPropertyAsString(ruleSetNode,
"jcr:description", false, problems);
+ String classname = tools.getPropertyAsString(ruleSetNode,
"dna:classname", true, problems);
+ String[] classpath = tools.getPropertyAsStringArray(ruleSetNode,
"dna:classpath", false, problems);
+ String providerUri = tools.getPropertyAsString(ruleSetNode,
"dna:serviceProviderUri", true, problems);
+ String ruleSetUri = tools.getPropertyAsString(ruleSetNode,
"dna:ruleSetUri", true, name, problems);
+ String rules = tools.getPropertyAsString(ruleSetNode, "dna:rules",
true, problems);
+ Map<String, Object> properties = tools.loadProperties(ruleSetNode,
problems);
+ if (problems.hasProblems()) {
+ // There are problems, so store and save them, and then return null ...
+ try {
+ if (tools.storeProblems(ruleSetNode, problems)) ruleSetNode.save();
+ } catch (RepositoryException e) {
+ this.logger.error(e, RepositoryI18n.errorWritingProblemsOnRuleSet,
tools.getReadable(ruleSetNode));
+ }
+ return null;
+ }
+ // There are no problems with this rule set, so make sure that there are no
persisted problems anymore ...
+ try {
+ if (tools.removeProblems(ruleSetNode)) ruleSetNode.save();
+ } catch (RepositoryException e) {
+ this.logger.error(e, RepositoryI18n.errorWritingProblemsOnRuleSet,
tools.getReadable(ruleSetNode));
+ }
+ return new RuleSet(name, description, classname, classpath, providerUri,
ruleSetUri, rules, properties);
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/InvalidSequencerPathExpression.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/InvalidSequencerPathExpression.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/InvalidSequencerPathExpression.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,62 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+
+/**
+ *
+ * @author Randall Hauch
+ */
+public class InvalidSequencerPathExpression extends RuntimeException {
+
+ /**
+ *
+ */
+ public InvalidSequencerPathExpression() {
+ }
+
+ /**
+ * @param message
+ */
+ public InvalidSequencerPathExpression( String message ) {
+ super(message);
+
+ }
+
+ /**
+ * @param cause
+ */
+ public InvalidSequencerPathExpression( Throwable cause ) {
+ super(cause);
+
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public InvalidSequencerPathExpression( String message, Throwable cause ) {
+ super(message, cause);
+
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/Sequencer.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/Sequencer.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/Sequencer.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,100 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import java.util.Set;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.component.Component;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.observation.NodeChangeListener;
+import org.jboss.dna.repository.observation.NodeChanges;
+import org.jboss.dna.repository.observation.ObservationService;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.RepositoryNodePath;
+
+/**
+ * The interface for a DNA sequencer, which sequences nodes and their content to extract
additional information from the
+ * information.
+ * <p>
+ * Implementations must provide a no-argument constructor.
+ * </p>
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public interface Sequencer extends Component<SequencerConfig> {
+
+ /**
+ * Execute the sequencing operation on the supplied node, which has recently been
created or changed. The implementation of
+ * this method is responsible for {@link ExecutionContext#getSessionFactory() getting
sessions}, modifying the appropriate
+ * nodes, {@link Session#save() saving} any changes made by this sequencer, and
{@link Session#logout() closing} all sessions
+ * (and any other acquired resources), even in the case of {@link
ProgressMonitor#isCancelled() cancellation} or exceptions.
+ * <p>
+ * The {@link SequencingService} determines the sequencers that should be executed by
monitoring the changes to one or more
+ * workspaces (it is a {@link NodeChangeListener} registered with the {@link
ObservationService}). Changes in those
+ * workspaces are aggregated for each transaction, and organized into {@link
NodeChanges changes for each node}. The
+ * SequencingService then determines for each {@link NodeChange set of changes to a
node} the set of full paths to the
+ * properties that have changed and whether those paths {@link
SequencerPathExpression#matcher(String) match} the sequencer's
+ * {@link SequencerConfig#getPathExpressions() path expressions}. Each path
expression produces the path to the output node,
+ * and these output paths are accumulated and (with the original node that changed,
the node change summary, and other
+ * information) supplied to the sequencer via this method.
+ * <p>
+ * It is possible that a sequencer is configured to apply to multiple properties on a
node. So, in cases where multiple
+ * properties are changed on a single node (within a single repository transaction),
the sequencer will only be executed once.
+ * Also, in such cases the sequencer's configuration may imply multiple output
nodes, so it is left to the sequencer to define
+ * the behavior in such cases.
+ * </p>
+ * <p>
+ * This operation should report progress to the supplied {@link ProgressMonitor}. At
the beginning of the operation, call
+ * {@link ProgressMonitor#beginTask(double, org.jboss.dna.common.i18n.I18n,
Object...)} with a meaningful message describing
+ * the operation and a total for the amount of work that will be done by this
sequencer. Then perform the sequencing work,
+ * periodically reporting work by specifying the {@link
ProgressMonitor#worked(double) amount of work} that has was just
+ * completed or by {@link ProgressMonitor#createSubtask(double) creating a subtask}
and reporting work against that subtask
+ * monitor.
+ * </p>
+ * <p>
+ * The implementation should also periodically check whether the operation has been
+ * {@link ProgressMonitor#isCancelled() cancelled}. If this method returns true, the
implementation should abort all work as
+ * soon as possible and close any resources that were acquired or opened.
+ * </p>
+ * <p>
+ * Finally, the implementation should call {@link ProgressMonitor#done()} when the
operation has finished.
+ * </p>
+ * @param input the node that has recently been created or changed; never null
+ * @param sequencedPropertyName the name of the property that caused this sequencer
to be executed; never null and never empty
+ * @param changes the immutable summary of changes that occurred on the
<code>input</code> node within the transaction;
+ * never null
+ * @param outputPaths the paths to the nodes where the sequencing content should be
placed; never null and never empty, but
+ * the set may contain paths for non-existant nodes or may reference the
<code>input</code> node
+ * @param context the context in which this sequencer is executing; never null
+ * @param progress the progress monitor that should be kept updated with the
sequencer's progress and that should be
+ * frequently consulted as to whether this operation has been {@link
ProgressMonitor#isCancelled() cancelled}.
+ * @throws RepositoryException if there is a problem while working with the
repository
+ * @throws SequencerException if there is an error in this sequencer
+ */
+ void execute( Node input, String sequencedPropertyName, NodeChange changes,
Set<RepositoryNodePath> outputPaths, ExecutionContext context, ProgressMonitor
progress )
+ throws RepositoryException, SequencerException;
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerConfig.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerConfig.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerConfig.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,75 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.component.ComponentConfig;
+
+/**
+ * @author Randall Hauch
+ */
+@Immutable
+public class SequencerConfig extends ComponentConfig {
+
+ private final Set<SequencerPathExpression> pathExpressions;
+
+ public SequencerConfig( String name, String description, String classname, String[]
classpath, String... pathExpressions ) {
+ this(name, description, System.currentTimeMillis(), classname, classpath,
pathExpressions);
+ }
+
+ public SequencerConfig( String name, String description, long timestamp, String
classname, String[] classpath, String... pathExpressions ) {
+ super(name, description, timestamp, classname, classpath);
+ this.pathExpressions = buildPathExpressionSet(pathExpressions);
+ }
+
+ /* package */static Set<SequencerPathExpression> buildPathExpressionSet(
String... pathExpressions ) {
+ Set<SequencerPathExpression> result = null;
+ if (pathExpressions != null) {
+ result = new LinkedHashSet<SequencerPathExpression>();
+ for (String pathExpression : pathExpressions) {
+ if (pathExpression == null) continue;
+ pathExpression = pathExpression.trim();
+ if (pathExpression.length() == 0) continue;
+ result.add(SequencerPathExpression.compile(pathExpression));
+ }
+ result = Collections.unmodifiableSet(result);
+ } else {
+ result = Collections.emptySet(); // already immutable
+ }
+ return result;
+ }
+
+ public Collection<SequencerPathExpression> getPathExpressions() {
+ return Collections.unmodifiableSet(this.pathExpressions);
+ }
+
+ public boolean hasChanged( SequencerConfig that ) {
+ if (super.hasChanged(that)) return true;
+ if (!this.getPathExpressions().equals(that.getPathExpressions())) return true;
+ return false;
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerException.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerException.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerException.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,56 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+/**
+ * @author Randall Hauch
+ */
+public class SequencerException extends RuntimeException {
+
+ /**
+ */
+ public SequencerException() {
+ }
+
+ /**
+ * @param message
+ */
+ public SequencerException( String message ) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public SequencerException( Throwable cause ) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public SequencerException( String message, Throwable cause ) {
+ super(message, cause);
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerOutputMap.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerOutputMap.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerOutputMap.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,316 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.NotThreadSafe;
+import org.jboss.dna.common.jcr.Path;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.spi.sequencers.SequencerOutput;
+
+/**
+ * A basic {@link SequencerOutput} that records all information in-memory and which
organizes the properties by
+ * {@link Path node paths} and provides access to the nodes in a natural path-order.
+ * @author Randall Hauch
+ */
+@NotThreadSafe
+public class SequencerOutputMap implements SequencerOutput,
Iterable<SequencerOutputMap.Entry> {
+
+ private static final String JCR_PRIMARY_TYPE_PROPERTY_NAME =
"jcr:primaryType";
+ private static final String JCR_NAME_PROPERTY_NAME = "jcr:name";
+
+ private final Map<Path, List<PropertyValue>> data;
+ private boolean valuesSorted = true;
+
+ public SequencerOutputMap() {
+ this.data = new HashMap<Path, List<PropertyValue>>();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setProperty( String nodePath, String property, Object... values ) {
+ property = property.trim();
+ if (JCR_NAME_PROPERTY_NAME.equals(property)) return; // ignore the
"jcr:name" property
+ nodePath = nodePath.trim();
+ if (nodePath.endsWith("/")) nodePath =
nodePath.replaceFirst("/+$", "");
+
+ // Find or create the entry for this node ...
+ Path path = new Path(nodePath);
+ List<PropertyValue> properties = this.data.get(path);
+ if (properties == null) {
+ if (values == null || values.length == 0) return; // do nothing
+ properties = new ArrayList<PropertyValue>();
+ this.data.put(path, properties);
+ }
+ if (values == null || values.length == 0) {
+ properties.remove(new PropertyValue(property, null));
+ } else {
+ Object propValue = values.length == 1 ? values[0] : values;
+ PropertyValue value = new PropertyValue(property, propValue);
+ properties.add(value);
+ valuesSorted = false;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setReference( String nodePath, String property, String... paths ) {
+ if (paths == null || paths.length == 0) {
+ setProperty(nodePath, property, (Object[])null);
+ } else if (paths.length == 1) {
+ setProperty(nodePath, property, new Path(paths[0]));
+ } else {
+ Path[] pathsArray = new Path[paths.length];
+ for (int i = 0; i != paths.length; ++i) {
+ pathsArray[i] = new Path(paths[i]);
+ }
+ setProperty(nodePath, property, (Object[])pathsArray);
+ }
+ }
+
+ /**
+ * Return the number of node entries in this map.
+ * @return the number of entries
+ */
+ public int size() {
+ return this.data.size();
+ }
+
+ /**
+ * Return whether there are no entries
+ * @return true if this container is empty, or false otherwise
+ */
+ public boolean isEmpty() {
+ return this.data.isEmpty();
+ }
+
+ protected List<PropertyValue> removeProperties( Path nodePath ) {
+ return this.data.remove(nodePath);
+ }
+
+ /**
+ * Get the properties for the node given by the supplied path.
+ * @param nodePath the path to the node
+ * @return the property values, or null if there are none
+ */
+ protected List<PropertyValue> getProperties( Path nodePath ) {
+ return data.get(nodePath);
+ }
+
+ /**
+ * Return the entries in this output in an order with shorter paths first.
+ * <p>
+ * {@inheritDoc}
+ */
+ public Iterator<Entry> iterator() {
+ LinkedList<Path> paths = new LinkedList<Path>(data.keySet());
+ Collections.sort(paths);
+ sortValues();
+ return new EntryIterator(paths.iterator());
+ }
+
+ protected void sortValues() {
+ if (!valuesSorted) {
+ for (List<PropertyValue> values : this.data.values()) {
+ if (values.size() > 1) Collections.sort(values);
+ }
+ valuesSorted = true;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return StringUtil.readableString(this.data);
+ }
+
+ /**
+ * A property name and value pair. PropertyValue instances have a natural order where
the <code>jcr:primaryType</code> is
+ * first, followed by all other properties in ascending lexicographical order
according to the {@link #getName() name}.
+ * @author Randall Hauch
+ */
+ @Immutable
+ public class PropertyValue implements Comparable<PropertyValue> {
+
+ private final String name;
+ private final Object value;
+
+ protected PropertyValue( String propertyName, Object value ) {
+ this.name = propertyName;
+ this.value = value;
+ }
+
+ /**
+ * Get the property name.
+ * @return the property name; never null
+ */
+ public String getName() {
+ return this.name;
+ }
+
+ /**
+ * Get the property value, which is either a single value or an array of values.
+ * @return the property value
+ */
+ public Object getValue() {
+ return this.value;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public int compareTo( PropertyValue that ) {
+ if (this == that) return 0;
+ if (this.name.equals(JCR_PRIMARY_TYPE_PROPERTY_NAME)) return -1;
+ if (that.name.equals(JCR_PRIMARY_TYPE_PROPERTY_NAME)) return 1;
+ return this.name.compareTo(that.name);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return this.name.hashCode();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof PropertyValue) {
+ PropertyValue that = (PropertyValue)obj;
+ if (!this.getName().equals(that.getName())) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return "[" + this.name + "=" +
StringUtil.readableString(value) + "]";
+ }
+ }
+
+ /**
+ * An entry in a SequencerOutputMap, which contains the path of the node and the
{@link #getPropertyValues() property values}
+ * on the node.
+ * @author Randall Hauch
+ */
+ @Immutable
+ public class Entry {
+
+ private final Path path;
+ private final String primaryType;
+ private final List<PropertyValue> properties;
+
+ protected Entry( Path path, List<PropertyValue> properties ) {
+ assert path != null;
+ assert properties != null;
+ this.path = path;
+ this.properties = properties;
+ if (this.properties.size() > 0 &&
this.properties.get(0).getName().equals("jcr:primaryType")) {
+ PropertyValue primaryTypeProperty = this.properties.remove(0);
+ this.primaryType = primaryTypeProperty.getValue().toString();
+ } else {
+ this.primaryType = null;
+ }
+ }
+
+ /**
+ * @return path
+ */
+ public Path getPath() {
+ return this.path;
+ }
+
+ /**
+ * Get the primary type specified for this node, or null if the type was not
specified
+ * @return the primary type, or null
+ */
+ public String getPrimaryTypeValue() {
+ return this.primaryType;
+ }
+
+ /**
+ * Get the property values, which may be empty
+ * @return value
+ */
+ public List<PropertyValue> getPropertyValues() {
+ return getProperties(path);
+ }
+ }
+
+ protected class EntryIterator implements Iterator<Entry> {
+
+ private Path last;
+ private final Iterator<Path> iter;
+
+ protected EntryIterator( Iterator<Path> iter ) {
+ this.iter = iter;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean hasNext() {
+ return iter.hasNext();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Entry next() {
+ this.last = iter.next();
+ return new Entry(last, getProperties(last));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void remove() {
+ if (last == null) throw new IllegalStateException();
+ try {
+ removeProperties(last);
+ } finally {
+ last = null;
+ }
+ }
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerPathExpression.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerPathExpression.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencerPathExpression.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,476 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.regex.Pattern;
+import java.util.regex.PatternSyntaxException;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.repository.RepositoryI18n;
+
+/**
+ * An expression that defines a selection of some change in the repository that signals a
sequencing operation should be run, and
+ * the location where the sequencing output should be placed. Sequencer path expressions
are used within the
+ * {@link SequencerConfig sequencer configurations} and used to determine whether
information in the repository needs to be
+ * sequenced.
+ * <p>
+ * A simple example is the following:
+ *
+ * <pre>
+ * /a/b/c@title => /d/e/f
+ * </pre>
+ *
+ * which means that a sequencer (that uses this expression in its configuration) should
be run any time there is a new or modified
+ * <code>title</code> property on the <code>/a/b/c</code> node,
and that the output of the sequencing should be placed at
+ * <code>/d/e/f</code>.
+ * </p>
+ * @author Randall Hauch
+ */
+@Immutable
+public class SequencerPathExpression implements Serializable {
+
+ /**
+ * The pattern used to break the initial input string into the two major parts, the
selection and output expressions. Group 1
+ * contains the selection expression, and group 2 contains the output expression.
+ */
+ private static final Pattern TWO_PART_PATTERN =
Pattern.compile("((?:[^=]|=(?!>))+)(?:=>(.+))?");
+
+ protected static final String DEFAULT_OUTPUT_EXPRESSION = ".";
+
+ private static final String REPLACEMENT_VARIABLE_PATTERN_STRING =
"(?<!\\\\)\\$(\\d+)"; // (?<!\\)\$(\d+)
+ private static final Pattern REPLACEMENT_VARIABLE_PATTERN =
Pattern.compile(REPLACEMENT_VARIABLE_PATTERN_STRING);
+
+ private static final String PARENT_PATTERN_STRING = "[^/]+/\\.\\./"; //
[^/]+/\.\./
+ private static final Pattern PARENT_PATTERN =
Pattern.compile(PARENT_PATTERN_STRING);
+
+ private static final String SEQUENCE_PATTERN_STRING =
"\\[(\\d+(?:,\\d+)*)\\]"; // \[(\d+(,\d+)*)\]
+ private static final Pattern SEQUENCE_PATTERN =
Pattern.compile(SEQUENCE_PATTERN_STRING);
+
+ /**
+ * Regular expression used to find unusable XPath predicates within an expression.
This pattern results in unusable predicates
+ * in group 1. Note that some predicates may be valid at the end but not valid
elsewhere.
+ * <p>
+ * Currently, only index-like predicates (including sequences) are allowed
everywhere. Predicates with paths and properties
+ * are allowed only as the last predicate. Predicates with any operators are unused.
+ * </p>
+ * <p>
+ * Nested predicates are not currently allowed.
+ * </p>
+ */
+ //
\[(?:(?:\d+(?:,\d+)*)|\*)\]|(?:\[[^\]\+\-\*=\!><'"\s]+\])$|(\[[^\]]+\])
+ private static final String UNUSABLE_PREDICATE_PATTERN_STRING =
"\\[(?:(?:\\d+(?:,\\d+)*)|\\*)\\]|(?:\\[[^\\]\\+\\-\\*=\\!><'\"\\s]+\\])$|(\\[[^\\]]+\\])";
+ private static final Pattern UNUSABLE_PREDICATE_PATTERN =
Pattern.compile(UNUSABLE_PREDICATE_PATTERN_STRING);
+
+ /**
+ * Regular expression used to find all XPath predicates except index and sequence
patterns. This pattern results in the
+ * predicates to be removed in group 1.
+ */
+ // \[(?:(?:\d+(?:,\d+)*)|\*)\]|(\[[^\]]+\])
+ private static final String NON_INDEX_PREDICATE_PATTERN_STRING =
"\\[(?:(?:\\d+(?:,\\d+)*)|\\*)\\]|(\\[[^\\]]+\\])";
+ private static final Pattern NON_INDEX_PREDICATE_PATTERN =
Pattern.compile(NON_INDEX_PREDICATE_PATTERN_STRING);
+
+ /**
+ * Compile the supplied expression and return the resulting SequencerPathExpression2
instance.
+ * @param expression the expression
+ * @return the path expression; never null
+ * @throws IllegalArgumentException if the expression is null
+ * @throws InvalidSequencerPathExpression if the expression is blank or is not a
valid expression
+ */
+ public static final SequencerPathExpression compile( String expression ) throws
InvalidSequencerPathExpression {
+ ArgCheck.isNotNull(expression, "sequencer path expression");
+ expression = expression.trim();
+ if (expression.length() == 0) {
+ throw new
InvalidSequencerPathExpression(RepositoryI18n.pathExpressionMayNotBeBlank.text());
+ }
+ java.util.regex.Matcher matcher = TWO_PART_PATTERN.matcher(expression);
+ if (!matcher.matches()) {
+ throw new
InvalidSequencerPathExpression(RepositoryI18n.pathExpressionIsInvalid.text(expression));
+ }
+ String selectExpression = matcher.group(1);
+ String outputExpression = matcher.group(2);
+ return new SequencerPathExpression(selectExpression, outputExpression);
+ }
+
+ private final String selectExpression;
+ private final String outputExpression;
+ private final Pattern matchPattern;
+ private final Pattern selectPattern;
+ private final int hc;
+
+ protected SequencerPathExpression( String selectExpression, String outputExpression )
throws InvalidSequencerPathExpression {
+ ArgCheck.isNotNull(selectExpression, "select expression");
+ this.selectExpression = selectExpression.trim();
+ this.outputExpression = outputExpression != null ? outputExpression.trim() :
DEFAULT_OUTPUT_EXPRESSION;
+ this.hc = HashCode.compute(this.selectExpression, this.outputExpression);
+
+ // Build the match pattern, which determines whether a path matches the condition
...
+ String matchString = this.selectExpression;
+ try {
+ matchString = removeUnusedPredicates(matchString);
+ matchString = replaceXPathPatterns(matchString);
+ this.matchPattern = Pattern.compile(matchString, Pattern.CASE_INSENSITIVE);
+ } catch (PatternSyntaxException e) {
+ String msg = RepositoryI18n.pathExpressionHasInvalidMatch.text(matchString,
this.selectExpression, this.outputExpression);
+ throw new InvalidSequencerPathExpression(msg, e);
+ }
+ // Build the select pattern, which determines the path that will be selected ...
+ String selectString = this.selectExpression.trim();
+ try {
+ selectString = removeAllPredicatesExceptIndexes(selectString);
+ selectString = replaceXPathPatterns(selectString);
+ selectString = "(" + selectString + ").*"; // group 1
will have selected path ...
+ this.selectPattern = Pattern.compile(selectString,
Pattern.CASE_INSENSITIVE);
+ } catch (PatternSyntaxException e) {
+ String msg = RepositoryI18n.pathExpressionHasInvalidSelect.text(selectString,
this.selectExpression, this.outputExpression);
+ throw new InvalidSequencerPathExpression(msg, e);
+ }
+ }
+
+ /**
+ * Replace certain XPath patterns that are not used or understood.
+ * @param expression the input regular expressions string; may not be null
+ * @return the regular expression with all unused XPath patterns removed; never null
+ */
+ protected String removeUnusedPredicates( String expression ) {
+ assert expression != null;
+ java.util.regex.Matcher matcher =
UNUSABLE_PREDICATE_PATTERN.matcher(expression);
+ StringBuffer sb = new StringBuffer();
+ if (matcher.find()) {
+ do {
+ // Remove those predicates that show up in group 1 ...
+ String predicateStr = matcher.group(0);
+ String unusablePredicateStr = matcher.group(1);
+ if (unusablePredicateStr != null) {
+ predicateStr = "";
+ }
+ matcher.appendReplacement(sb, predicateStr);
+ } while (matcher.find());
+ matcher.appendTail(sb);
+ expression = sb.toString();
+ }
+ return expression;
+ }
+
+ /**
+ * Remove all XPath predicates from the supplied regular expression string.
+ * @param expression the input regular expressions string; may not be null
+ * @return the regular expression with all XPath predicates removed; never null
+ */
+ protected String removeAllPredicatesExceptIndexes( String expression ) {
+ assert expression != null;
+ java.util.regex.Matcher matcher =
NON_INDEX_PREDICATE_PATTERN.matcher(expression);
+ StringBuffer sb = new StringBuffer();
+ if (matcher.find()) {
+ do {
+ // Remove those predicates that show up in group 1 ...
+ String predicateStr = matcher.group(0);
+ String unusablePredicateStr = matcher.group(1);
+ if (unusablePredicateStr != null) {
+ predicateStr = "";
+ }
+ matcher.appendReplacement(sb, predicateStr);
+ } while (matcher.find());
+ matcher.appendTail(sb);
+ expression = sb.toString();
+ }
+ return expression;
+ }
+
+ /**
+ * Replace certain XPath patterns, including some predicates, with substrings that
are compatible with regular expressions.
+ * @param expression the input regular expressions string; may not be null
+ * @return the regular expression with XPath patterns replaced with regular
expression fragments; never null
+ */
+ protected String replaceXPathPatterns( String expression ) {
+ assert expression != null;
+ // replace 2 or more sequential '|' characters in an OR expression
+ expression = expression.replaceAll("[\\|]{2,}", "|");
+ // if there is an empty expression in an OR expression, make the whole segment
optional ...
+ // (e.g., "/a/b/(c|)/d" => "a/b(/(c))?/d"
+ expression = expression.replaceAll("/(\\([^|]+)(\\|){2,}([^)]+\\))",
"(/$1$2$3)?");
+ expression = expression.replaceAll("/\\(\\|+([^)]+)\\)",
"(/($1))?");
+ expression = expression.replaceAll("/\\((([^|]+)(\\|[^|]+)*)\\|+\\)",
"(/($1))?");
+
+ // // Allow any path (that doesn't contain an explicit counter) to contain a
counter,
+ // // done by replacing any '/' or '|' that isn't preceded by
']' or '*' or '/' or '(' with '(\[\d+\])?/'...
+ // input = input.replaceAll("(?<=[^\\]\\*/(])([/|])",
"(?:\\\\[\\\\d+\\\\])?$1");
+
+ // Does the path contain any '[]' or '[*]' or '[0]' or
'[n]' (where n is any positive integers)...
+ // '[*]/' => '(\[\d+\])?/'
+ expression = expression.replaceAll("\\[\\]",
"(?:\\\\[\\\\d+\\\\])?"); // index is optional
+ // '[]/' => '(\[\d+\])?/'
+ expression = expression.replaceAll("\\[[*]\\]",
"(?:\\\\[\\\\d+\\\\])?"); // index is optional
+ // '[0]/' => '(\[0\])?/'
+ expression = expression.replaceAll("\\[0\\]",
"(?:\\\\[0\\\\])?"); // index is optional
+ // '[n]/' => '\[n\]/'
+ expression = expression.replaceAll("\\[([1-9]\\d*)\\]",
"\\\\[$1\\\\]"); // index is required
+
+ // Change any other end predicates to not be wrapped by braces but to begin with
a slash ...
+ // ...'[x]' => ...'/x'
+ expression = expression.replaceAll("(?<!\\\\)\\[([^\\]]*)\\]$",
"/$1");
+
+ // Replace all '[n,m,o,p]' type sequences with '[(n|m|o|p)]'
+ java.util.regex.Matcher matcher = SEQUENCE_PATTERN.matcher(expression);
+ StringBuffer sb = new StringBuffer();
+ boolean result = matcher.find();
+ if (result) {
+ do {
+ String sequenceStr = matcher.group(1);
+ boolean optional = false;
+ if (sequenceStr.startsWith("0,")) {
+ sequenceStr = sequenceStr.replaceFirst("^0,",
"");
+ optional = true;
+ }
+ if (sequenceStr.endsWith(",0")) {
+ sequenceStr = sequenceStr.replaceFirst(",0$",
"");
+ optional = true;
+ }
+ if (sequenceStr.contains(",0,")) {
+ sequenceStr = sequenceStr.replaceAll(",0,",
",");
+ optional = true;
+ }
+ sequenceStr = sequenceStr.replaceAll(",", "|");
+ String replacement = "\\\\[(?:" + sequenceStr +
")\\\\]";
+ if (optional) {
+ replacement = "(?:" + replacement + ")?";
+ }
+ matcher.appendReplacement(sb, replacement);
+ result = matcher.find();
+ } while (result);
+ matcher.appendTail(sb);
+ expression = sb.toString();
+ }
+
+ // Order is important here
+ expression = expression.replaceAll("[*]", "[^/]*");
+ expression = expression.replaceAll("[/]{2,}", "(/[^/]*)*/");
+ return expression;
+ }
+
+ /**
+ * @return selectExpression
+ */
+ public String getSelectExpression() {
+ return this.selectExpression;
+ }
+
+ /**
+ * @return outputExpression
+ */
+ public String getOutputExpression() {
+ return this.outputExpression;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return this.hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SequencerPathExpression) {
+ SequencerPathExpression that = (SequencerPathExpression)obj;
+ if (!this.selectExpression.equalsIgnoreCase(that.selectExpression)) return
false;
+ if (!this.outputExpression.equalsIgnoreCase(that.outputExpression)) return
false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return this.selectExpression + "=>" + this.outputExpression;
+ }
+
+ /**
+ * @param absolutePath
+ * @return the matcher
+ */
+ public Matcher matcher( String absolutePath ) {
+ // Determine if the input path match the select expression ...
+ String originalAbsolutePath = absolutePath;
+ // if (!absolutePath.endsWith("/")) absolutePath = absolutePath +
"/";
+ // Remove all trailing '/' ...
+ absolutePath = absolutePath.replaceAll("/+$", "");
+
+ // See if the supplied absolute path matches the pattern ...
+ final java.util.regex.Matcher matcher = this.matchPattern.matcher(absolutePath);
+ if (!matcher.matches()) {
+ // No match, so return immediately ...
+ return new Matcher(originalAbsolutePath, null, null);
+ }
+ Map<Integer, String> replacements = new HashMap<Integer, String>();
+ for (int i = 0, count = matcher.groupCount(); i <= count; ++i) {
+ replacements.put(i, matcher.group(i));
+ }
+
+ // The absolute path does match the pattern, so use the select pattern and try to
grab the selected path ...
+ final java.util.regex.Matcher selectMatcher =
this.selectPattern.matcher(absolutePath);
+ if (!selectMatcher.matches()) {
+ // Nothing can be selected, so return immediately ...
+ return new Matcher(originalAbsolutePath, null, null);
+ }
+ // Grab the selected path ...
+ String selectedPath = selectMatcher.group(1);
+
+ // Remove the trailing '/@property' ...
+ selectedPath = selectedPath.replaceAll("/@[^/\\[\\]]+$",
"");
+
+ // Find the output path using the groups from the match pattern ...
+ String outputPath = this.outputExpression;
+ if (!DEFAULT_OUTPUT_EXPRESSION.equals(outputPath)) {
+ java.util.regex.Matcher replacementMatcher =
REPLACEMENT_VARIABLE_PATTERN.matcher(outputPath);
+ StringBuffer sb = new StringBuffer();
+ if (replacementMatcher.find()) {
+ do {
+ String variable = replacementMatcher.group(1);
+ String replacement = replacements.get(Integer.valueOf(variable));
+ if (replacement == null) replacement = replacementMatcher.group(0);
+ replacementMatcher.appendReplacement(sb, replacement);
+ } while (replacementMatcher.find());
+ replacementMatcher.appendTail(sb);
+ outputPath = sb.toString();
+ }
+ // Make sure there is a trailing '/' ...
+ if (!outputPath.endsWith("/")) outputPath = outputPath +
"/";
+
+ // Replace all references to "/./" with "/" ...
+ outputPath = outputPath.replaceAll("/\\./", "/");
+
+ // Remove any path segment followed by a parent reference ...
+ java.util.regex.Matcher parentMatcher = PARENT_PATTERN.matcher(outputPath);
+ while (parentMatcher.find()) {
+ outputPath = parentMatcher.replaceAll("");
+ // Make sure there is a trailing '/' ...
+ if (!outputPath.endsWith("/")) outputPath = outputPath +
"/";
+ parentMatcher = PARENT_PATTERN.matcher(outputPath);
+ }
+
+ // Remove all multiple occurrences of '/' ...
+ outputPath = outputPath.replaceAll("/{2,}", "/");
+
+ // Remove the trailing '/@property' ...
+ outputPath = outputPath.replaceAll("/@[^/\\[\\]]+$",
"");
+
+ // Remove a trailing '/' ...
+ outputPath = outputPath.replaceAll("/$", "");
+
+ // If the output path is blank, then use the default output expression ...
+ if (outputPath.length() == 0) outputPath = DEFAULT_OUTPUT_EXPRESSION;
+
+ }
+ if (DEFAULT_OUTPUT_EXPRESSION.equals(outputPath)) {
+ // The output path is the default expression, so use the selected path ...
+ outputPath = selectedPath;
+ }
+
+ return new Matcher(originalAbsolutePath, selectedPath, outputPath);
+ }
+
+ @Immutable
+ public static class Matcher {
+
+ private final String inputPath;
+ private final String selectedPath;
+ private final String outputPath;
+ private final int hc;
+
+ protected Matcher( String inputPath, String selectedPath, String outputPath ) {
+ this.inputPath = inputPath;
+ this.selectedPath = selectedPath;
+ this.outputPath = outputPath;
+ this.hc = HashCode.compute(this.inputPath, this.selectedPath,
this.outputPath);
+ }
+
+ public boolean matches() {
+ return this.selectedPath != null && this.outputPath != null;
+ }
+
+ /**
+ * @return inputPath
+ */
+ public String getInputPath() {
+ return this.inputPath;
+ }
+
+ /**
+ * @return selectPattern
+ */
+ public String getSelectedPath() {
+ return this.selectedPath;
+ }
+
+ /**
+ * @return outputPath
+ */
+ public String getOutputPath() {
+ return this.outputPath;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return this.hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SequencerPathExpression.Matcher) {
+ SequencerPathExpression.Matcher that =
(SequencerPathExpression.Matcher)obj;
+ if (!this.inputPath.equalsIgnoreCase(that.inputPath)) return false;
+ if (!this.selectedPath.equalsIgnoreCase(that.selectedPath)) return
false;
+ if (!this.outputPath.equalsIgnoreCase(that.outputPath)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return this.selectedPath + " => " + this.outputPath;
+ }
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencingService.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencingService.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/SequencingService.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,710 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import net.jcip.annotations.Immutable;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.component.ClassLoaderFactory;
+import org.jboss.dna.common.component.ComponentLibrary;
+import org.jboss.dna.common.component.StandardClassLoaderFactory;
+import org.jboss.dna.common.monitor.LoggingProgressMonitor;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+import org.jboss.dna.common.monitor.SimpleProgressMonitor;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.repository.RepositoryI18n;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.observation.NodeChangeListener;
+import org.jboss.dna.repository.observation.NodeChanges;
+import org.jboss.dna.repository.services.AbstractServiceAdministrator;
+import org.jboss.dna.repository.services.AdministeredService;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.JcrTools;
+import org.jboss.dna.repository.util.RepositoryNodePath;
+import org.jboss.dna.repository.util.SessionFactory;
+
+/**
+ * A sequencing system is used to monitor changes in the content of {@link Repository JCR
repositories} and to sequence the
+ * content to extract or to generate structured information.
+ * @author Randall Hauch
+ */
+public class SequencingService implements AdministeredService, NodeChangeListener {
+
+ /**
+ * Interface used to select the set of {@link Sequencer} instances that should be
run.
+ * @author Randall Hauch
+ */
+ public static interface Selector {
+
+ /**
+ * Select the sequencers that should be used to sequence the supplied node.
+ * @param sequencers the list of all sequencers available at the moment; never
null
+ * @param node the node to be sequenced; never null
+ * @param nodeChange the set of node changes; never null
+ * @return the list of sequencers that should be used; may not be null
+ */
+ List<Sequencer> selectSequencers( List<Sequencer> sequencers, Node
node, NodeChange nodeChange );
+ }
+
+ /**
+ * The default {@link Selector} implementation that selects every sequencer every
time it's called, regardless of the node (or
+ * logger) supplied.
+ * @author Randall Hauch
+ */
+ protected static class DefaultSelector implements Selector {
+
+ public List<Sequencer> selectSequencers( List<Sequencer> sequencers,
Node node, NodeChange nodeChange ) {
+ return sequencers;
+ }
+ }
+
+ /**
+ * Interface used to determine whether a {@link NodeChange} should be processed.
+ * @author Randall Hauch
+ */
+ public static interface NodeFilter {
+
+ /**
+ * Determine whether the node represented by the supplied change should be
submitted for sequencing.
+ * @param nodeChange the node change event
+ * @return true if the node should be submitted for sequencing, or false if the
change should be ignored
+ */
+ boolean accept( NodeChange nodeChange );
+ }
+
+ /**
+ * The default filter implementation, which accepts only new nodes or nodes that have
new or changed properties.
+ * @author Randall Hauch
+ */
+ protected static class DefaultNodeFilter implements NodeFilter {
+
+ public boolean accept( NodeChange nodeChange ) {
+ // Only care about new nodes or nodes that have new/changed properies ...
+ return nodeChange.includesEventTypes(Event.NODE_ADDED, Event.PROPERTY_ADDED,
Event.PROPERTY_CHANGED);
+ }
+ }
+
+ /**
+ * The default {@link Selector} that considers every {@link Sequencer} to be used for
every node.
+ * @see
SequencingService#setSequencerSelector(org.jboss.dna.repository.sequencers.SequencingService.Selector)
+ */
+ public static final Selector DEFAULT_SEQUENCER_SELECTOR = new DefaultSelector();
+ /**
+ * The default {@link NodeFilter} that accepts new nodes or nodes that have
new/changed properties.
+ * @see
SequencingService#setSequencerSelector(org.jboss.dna.repository.sequencers.SequencingService.Selector)
+ */
+ public static final NodeFilter DEFAULT_NODE_FILTER = new DefaultNodeFilter();
+
+ /**
+ * Class loader factory instance that always returns the
+ * {@link Thread#getContextClassLoader() current thread's context class loader}
(if not null) or component library's class
+ * loader.
+ */
+ protected static final ClassLoaderFactory DEFAULT_CLASSLOADER_FACTORY = new
StandardClassLoaderFactory(SequencingService.class.getClassLoader());
+
+ /**
+ * The administrative component for this service.
+ * @author Randall Hauch
+ */
+ protected class Administrator extends AbstractServiceAdministrator {
+
+ protected Administrator() {
+ super(State.PAUSED);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected String serviceName() {
+ return RepositoryI18n.sequencingServiceName.text();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void doStart( State fromState ) {
+ super.doStart(fromState);
+ startService();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void doShutdown( State fromState ) {
+ super.doShutdown(fromState);
+ shutdownService();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public boolean awaitTermination( long timeout, TimeUnit unit ) throws
InterruptedException {
+ return doAwaitTermination(timeout, unit);
+ }
+
+ }
+
+ private ExecutionContext executionContext;
+ private ComponentLibrary<Sequencer, SequencerConfig> sequencerLibrary = new
ComponentLibrary<Sequencer, SequencerConfig>();
+ private Selector sequencerSelector = DEFAULT_SEQUENCER_SELECTOR;
+ private NodeFilter nodeFilter = DEFAULT_NODE_FILTER;
+ private ExecutorService executorService;
+ private Logger logger = Logger.getLogger(this.getClass());
+ private final Statistics statistics = new Statistics();
+ private final Administrator administrator = new Administrator();
+
+ /**
+ * Create a new sequencing system, configured with no sequencers and not monitoring
any workspaces. Upon construction, the
+ * system is {@link ServiceAdministrator#isPaused() paused} and must be configured
and then
+ * {@link ServiceAdministrator#start() started}.
+ */
+ public SequencingService() {
+ this.sequencerLibrary.setClassLoaderFactory(DEFAULT_CLASSLOADER_FACTORY);
+ }
+
+ /**
+ * Return the administrative component for this service.
+ * @return the administrative component; never null
+ */
+ public ServiceAdministrator getAdministrator() {
+ return this.administrator;
+ }
+
+ /**
+ * Get the statistics for this system.
+ * @return statistics
+ */
+ public Statistics getStatistics() {
+ return this.statistics;
+ }
+
+ /**
+ * @return sequencerLibrary
+ */
+ protected ComponentLibrary<Sequencer, SequencerConfig> getSequencerLibrary() {
+ return this.sequencerLibrary;
+ }
+
+ /**
+ * Get the class loader factory that should be used to load sequencers. By default,
this service uses a factory that will
+ * return either the {@link Thread#getContextClassLoader() current thread's
context class loader} (if not null) or the class
+ * loader that loaded this class.
+ * @return the class loader factory; never null
+ * @see #setClassLoaderFactory(ClassLoaderFactory)
+ */
+ public ClassLoaderFactory getClassLoaderFactory() {
+ return this.sequencerLibrary.getClassLoaderFactory();
+ }
+
+ /**
+ * Set the Maven Repository that should be used to load the sequencer classes. By
default, this service uses a class loader
+ * factory that will return either the {@link Thread#getContextClassLoader() current
thread's context class loader} (if not
+ * null) or the class loader that loaded this class.
+ * @param classLoaderFactory the class loader factory reference, or null if the
default class loader factory should be used.
+ * @see #getClassLoaderFactory()
+ */
+ public void setClassLoaderFactory( ClassLoaderFactory classLoaderFactory ) {
+ this.sequencerLibrary.setClassLoaderFactory(classLoaderFactory != null ?
classLoaderFactory : DEFAULT_CLASSLOADER_FACTORY);
+ }
+
+ /**
+ * Add the configuration for a sequencer, or update any existing one that represents
the
+ * {@link SequencerConfig#equals(Object) same configuration}
+ * @param config the new configuration
+ * @return true if the sequencer was added, or false if there already was an existing
and
+ * {@link SequencerConfig#hasChanged(SequencerConfig) unchanged} sequencer
configuration
+ * @throws IllegalArgumentException if <code>config</code> is null
+ * @see #updateSequencer(SequencerConfig)
+ * @see #removeSequencer(SequencerConfig)
+ */
+ public boolean addSequencer( SequencerConfig config ) {
+ return this.sequencerLibrary.add(config);
+ }
+
+ /**
+ * Update the configuration for a sequencer, or add it if there is no
+ * {@link SequencerConfig#equals(Object) matching configuration}.
+ * @param config the updated (or new) configuration
+ * @return true if the sequencer was updated, or false if there already was an
existing and
+ * {@link SequencerConfig#hasChanged(SequencerConfig) unchanged} sequencer
configuration
+ * @throws IllegalArgumentException if <code>config</code> is null
+ * @see #addSequencer(SequencerConfig)
+ * @see #removeSequencer(SequencerConfig)
+ */
+ public boolean updateSequencer( SequencerConfig config ) {
+ return this.sequencerLibrary.update(config);
+ }
+
+ /**
+ * Remove the configuration for a sequencer.
+ * @param config the configuration to be removed
+ * @return true if the sequencer was removed, or false if there was no existing
sequencer
+ * @throws IllegalArgumentException if <code>config</code> is null
+ * @see #addSequencer(SequencerConfig)
+ * @see #updateSequencer(SequencerConfig)
+ */
+ public boolean removeSequencer( SequencerConfig config ) {
+ return this.sequencerLibrary.remove(config);
+ }
+
+ /**
+ * Get the logger for this system
+ * @return the logger
+ */
+ public Logger getLogger() {
+ return this.logger;
+ }
+
+ /**
+ * Set the logger for this system.
+ * @param logger the logger, or null if the standard logging should be used
+ */
+ public void setLogger( Logger logger ) {
+ this.logger = logger != null ? logger : Logger.getLogger(this.getClass());
+ }
+
+ /**
+ * @return executionContext
+ */
+ public ExecutionContext getExecutionContext() {
+ return this.executionContext;
+ }
+
+ /**
+ * @param executionContext Sets executionContext to the specified value.
+ */
+ public void setExecutionContext( ExecutionContext executionContext ) {
+ ArgCheck.isNotNull(executionContext, "execution context");
+ if (this.getAdministrator().isStarted()) {
+ throw new
IllegalStateException(RepositoryI18n.unableToChangeExecutionContextWhileRunning.text());
+ }
+ this.executionContext = executionContext;
+ }
+
+ /**
+ * Get the executor service used to run the sequencers.
+ * @return the executor service
+ * @see #setExecutorService(ExecutorService)
+ */
+ public ExecutorService getExecutorService() {
+ return this.executorService;
+ }
+
+ /**
+ * Set the executor service that should be used by this system. By default, the
system is set up with a
+ * {@link Executors#newSingleThreadExecutor() executor that uses a single thread}.
+ * @param executorService the executor service
+ * @see #getExecutorService()
+ * @see Executors#newCachedThreadPool()
+ * @see Executors#newCachedThreadPool(java.util.concurrent.ThreadFactory)
+ * @see Executors#newFixedThreadPool(int)
+ * @see Executors#newFixedThreadPool(int, java.util.concurrent.ThreadFactory)
+ * @see Executors#newScheduledThreadPool(int)
+ * @see Executors#newScheduledThreadPool(int, java.util.concurrent.ThreadFactory)
+ * @see Executors#newSingleThreadExecutor()
+ * @see Executors#newSingleThreadExecutor(java.util.concurrent.ThreadFactory)
+ * @see Executors#newSingleThreadScheduledExecutor()
+ * @see
Executors#newSingleThreadScheduledExecutor(java.util.concurrent.ThreadFactory)
+ */
+ public void setExecutorService( ExecutorService executorService ) {
+ ArgCheck.isNotNull(executorService, "executor service");
+ if (this.getAdministrator().isStarted()) {
+ throw new
IllegalStateException(RepositoryI18n.unableToChangeExecutionContextWhileRunning.text());
+ }
+ this.executorService = executorService;
+ }
+
+ /**
+ * Override this method to creates a different kind of default executor service. This
method is called when the system is
+ * {@link #start() started} without an executor service being {@link
#setExecutorService(ExecutorService) set}.
+ * <p>
+ * This method creates a {@link Executors#newSingleThreadExecutor() single-threaded
executor}.
+ * </p>
+ * @return
+ */
+ protected ExecutorService createDefaultExecutorService() {
+ return Executors.newSingleThreadExecutor();
+ }
+
+ protected void startService() {
+ if (this.getExecutionContext() == null) {
+ throw new
IllegalStateException(RepositoryI18n.unableToStartSequencingServiceWithoutExecutionContext.text());
+ }
+ if (this.executorService == null) {
+ this.executorService = createDefaultExecutorService();
+ }
+ assert this.executorService != null;
+ assert this.sequencerSelector != null;
+ assert this.nodeFilter != null;
+ assert this.sequencerLibrary != null;
+ }
+
+ protected void shutdownService() {
+ if (this.executorService != null) {
+ this.executorService.shutdown();
+ }
+ }
+
+ protected boolean doAwaitTermination( long timeout, TimeUnit unit ) throws
InterruptedException {
+ if (this.executorService.isShutdown()) return true;
+ return this.executorService.awaitTermination(timeout, unit);
+ }
+
+ /**
+ * Get the sequencing selector used by this system.
+ * @return the sequencing selector
+ */
+ public Selector getSequencerSelector() {
+ return this.sequencerSelector;
+ }
+
+ /**
+ * Set the sequencer selector, or null if the {@link #DEFAULT_SEQUENCER_SELECTOR
default sequencer selector} should be used.
+ * @param sequencerSelector the selector
+ */
+ public void setSequencerSelector( Selector sequencerSelector ) {
+ this.sequencerSelector = sequencerSelector != null ? sequencerSelector :
DEFAULT_SEQUENCER_SELECTOR;
+ }
+
+ /**
+ * Get the node filter used by this system.
+ * @return the node filter
+ */
+ public NodeFilter getNodeFilter() {
+ return this.nodeFilter;
+ }
+
+ /**
+ * Set the filter that checks which nodes are to be sequenced, or null if the {@link
#DEFAULT_NODE_FILTER default node filter}
+ * should be used.
+ * @param nodeFilter the new node filter
+ */
+ public void setNodeFilter( NodeFilter nodeFilter ) {
+ this.nodeFilter = nodeFilter != null ? nodeFilter : DEFAULT_NODE_FILTER;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void onNodeChanges( NodeChanges changes ) {
+ NodeFilter filter = this.getNodeFilter();
+ for (final NodeChange changedNode : changes) {
+ // Only care about new nodes or nodes that have new/changed properies ...
+ if (filter.accept(changedNode)) {
+ this.executorService.execute(new Runnable() {
+
+ public void run() {
+ processChangedNode(changedNode);
+ }
+ });
+ }
+ }
+ }
+
+ /**
+ * Do the work of processing by sequencing the node. This method is called by the
{@link #executorService executor service}
+ * when it performs it's work on the enqueued {@link ChangedNode ChangedNode
runnable objects}.
+ * @param node the node to be processed.
+ */
+ protected void processChangedNode( NodeChange changedNode ) {
+ try {
+ final String repositoryWorkspaceName =
changedNode.getRepositoryWorkspaceName();
+ Session session = null;
+ try {
+ // Figure out which sequencers accept this path,
+ // and track which output nodes should be passed to each sequencer...
+ final String nodePath = changedNode.getAbsolutePath();
+ Map<SequencerCall, Set<RepositoryNodePath>> sequencerCalls =
new HashMap<SequencerCall, Set<RepositoryNodePath>>();
+ List<Sequencer> allSequencers =
this.sequencerLibrary.getInstances();
+ List<Sequencer> sequencers = new
ArrayList<Sequencer>(allSequencers.size());
+ for (Sequencer sequencer : allSequencers) {
+ final SequencerConfig config = sequencer.getConfiguration();
+ for (SequencerPathExpression pathExpression :
config.getPathExpressions()) {
+ for (String propertyName : changedNode.getModifiedProperties())
{
+ String path = nodePath + "/@" + propertyName;
+ SequencerPathExpression.Matcher matcher =
pathExpression.matcher(path);
+ if (matcher.matches()) {
+ // String selectedPath = matcher.getSelectedPath();
+ RepositoryNodePath outputPath =
RepositoryNodePath.parse(matcher.getOutputPath(), repositoryWorkspaceName);
+ SequencerCall call = new SequencerCall(sequencer,
propertyName);
+ // Record the output path ...
+ Set<RepositoryNodePath> outputPaths =
sequencerCalls.get(call);
+ if (outputPaths == null) {
+ outputPaths = new
HashSet<RepositoryNodePath>();
+ sequencerCalls.put(call, outputPaths);
+ }
+ outputPaths.add(outputPath);
+ sequencers.add(sequencer);
+ break;
+ }
+ }
+ }
+ }
+
+ Node node = null;
+ if (!sequencers.isEmpty()) {
+ // Create a session that we'll use for all sequencing ...
+ session =
this.getExecutionContext().getSessionFactory().createSession(repositoryWorkspaceName);
+
+ // Find the changed node ...
+ String relPath =
changedNode.getAbsolutePath().replaceAll("^/+", "");
+ node = session.getRootNode().getNode(relPath);
+
+ // Figure out which sequencers should run ...
+ sequencers = this.sequencerSelector.selectSequencers(sequencers,
node, changedNode);
+ }
+ if (sequencers.isEmpty()) {
+ this.statistics.recordNodeSkipped();
+ if (this.logger.isDebugEnabled()) {
+ this.logger.trace("Skipping '{}': no sequencers
matched this condition", changedNode);
+ }
+ } else {
+ // Run each of those sequencers ...
+ ProgressMonitor progressMonitor = new
SimpleProgressMonitor(RepositoryI18n.sequencerTask.text(changedNode));
+ if (this.logger.isTraceEnabled()) {
+ progressMonitor = new LoggingProgressMonitor(progressMonitor,
this.logger, Logger.Level.TRACE);
+ }
+ try {
+ progressMonitor.beginTask(sequencerCalls.size(),
RepositoryI18n.sequencerTask, changedNode);
+ for (Map.Entry<SequencerCall,
Set<RepositoryNodePath>> entry : sequencerCalls.entrySet()) {
+ final SequencerCall sequencerCall = entry.getKey();
+ final Set<RepositoryNodePath> outputPaths =
entry.getValue();
+ final Sequencer sequencer = sequencerCall.getSequencer();
+ final String sequencerName =
sequencer.getConfiguration().getName();
+ final String propertyName =
sequencerCall.getSequencedPropertyName();
+
+ // Get the paths to the nodes where the sequencer should
write it's output ...
+ assert outputPaths != null && outputPaths.size() !=
0;
+
+ // Create a new execution context for each sequencer
+ final Context executionContext = new Context();
+ final ProgressMonitor sequenceMonitor =
progressMonitor.createSubtask(1);
+ try {
+ sequenceMonitor.beginTask(100,
RepositoryI18n.sequencerSubtask, sequencerName);
+ sequencer.execute(node, propertyName, changedNode,
outputPaths, executionContext, sequenceMonitor.createSubtask(80)); // 80%
+ } catch (RepositoryException e) {
+ this.logger.error(e,
RepositoryI18n.errorInRepositoryWhileSequencingNode, sequencerName, changedNode);
+ } catch (SequencerException e) {
+ this.logger.error(e,
RepositoryI18n.errorWhileSequencingNode, sequencerName, changedNode);
+ } finally {
+ try {
+ // Save the changes made by each sequencer ...
+ if (session != null) session.save();
+ sequenceMonitor.worked(10); // 90% of
sequenceMonitor
+ } finally {
+ try {
+ // And always close the context.
+ // This closes all sessions that may have been
created by the sequencer.
+ executionContext.close();
+ } finally {
+ sequenceMonitor.done(); // 100% of
sequenceMonitor
+ }
+ }
+ }
+ }
+ this.statistics.recordNodeSequenced();
+ } finally {
+ progressMonitor.done();
+ }
+ }
+ } finally {
+ if (session != null) session.logout();
+ }
+ } catch (RepositoryException e) {
+ this.logger.error(e,
RepositoryI18n.errorInRepositoryWhileFindingSequencersToRunAgainstNode, changedNode);
+ } catch (Exception e) {
+ this.logger.error(e, RepositoryI18n.errorFindingSequencersToRunAgainstNode,
changedNode);
+ }
+ }
+
+ protected class Context implements ExecutionContext {
+
+ protected final SessionFactory factory;
+ private final Set<Session> sessions = new HashSet<Session>();
+ protected final AtomicBoolean closed = new AtomicBoolean(false);
+
+ protected Context() {
+ final SessionFactory delegate =
SequencingService.this.getExecutionContext().getSessionFactory();
+ this.factory = new SessionFactory() {
+
+ public Session createSession( String name ) throws RepositoryException {
+ if (closed.get()) throw new
IllegalStateException(RepositoryI18n.executionContextHasBeenClosed.text());
+ Session session = delegate.createSession(name);
+ recordSession(session);
+ return session;
+ }
+ };
+ }
+
+ protected synchronized void recordSession( Session session ) {
+ if (session != null) sessions.add(session);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SessionFactory getSessionFactory() {
+ return this.factory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JcrTools getTools() {
+ return SequencingService.this.getExecutionContext().getTools();
+ }
+
+ public synchronized void close() {
+ if (this.closed.get()) return;
+ this.closed.set(true);
+ for (Session session : sessions) {
+ if (session != null) session.logout();
+ }
+ }
+ }
+
+ /**
+ * The statistics for the system. Each sequencing system has an instance of this
class that is updated.
+ * @author Randall Hauch
+ */
+ @ThreadSafe
+ public class Statistics {
+
+ private final AtomicLong numberOfNodesSequenced = new AtomicLong(0);
+ private final AtomicLong numberOfNodesSkipped = new AtomicLong(0);
+ private final AtomicLong startTime;
+
+ protected Statistics() {
+ startTime = new AtomicLong(System.currentTimeMillis());
+ }
+
+ public Statistics reset() {
+ this.startTime.set(System.currentTimeMillis());
+ this.numberOfNodesSequenced.set(0);
+ this.numberOfNodesSkipped.set(0);
+ return this;
+ }
+
+ /**
+ * @return the system time when the statistics were started
+ */
+ public long getStartTime() {
+ return this.startTime.get();
+ }
+
+ /**
+ * @return the number of nodes that were sequenced
+ */
+ public long getNumberOfNodesSequenced() {
+ return this.numberOfNodesSequenced.get();
+ }
+
+ /**
+ * @return the number of nodes that were skipped because no sequencers applied
+ */
+ public long getNumberOfNodesSkipped() {
+ return this.numberOfNodesSkipped.get();
+ }
+
+ protected void recordNodeSequenced() {
+ this.numberOfNodesSequenced.incrementAndGet();
+ }
+
+ protected void recordNodeSkipped() {
+ this.numberOfNodesSkipped.incrementAndGet();
+ }
+ }
+
+ @Immutable
+ protected class SequencerCall {
+
+ private final Sequencer sequencer;
+ private final String sequencerName;
+ private final String sequencedPropertyName;
+ private final int hc;
+
+ protected SequencerCall( Sequencer sequencer, String sequencedPropertyName ) {
+ this.sequencer = sequencer;
+ this.sequencerName = sequencer.getConfiguration().getName();
+ this.sequencedPropertyName = sequencedPropertyName;
+ this.hc = HashCode.compute(this.sequencerName, this.sequencedPropertyName);
+ }
+
+ /**
+ * @return sequencer
+ */
+ public Sequencer getSequencer() {
+ return this.sequencer;
+ }
+
+ /**
+ * @return sequencedPropertyName
+ */
+ public String getSequencedPropertyName() {
+ return this.sequencedPropertyName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return this.hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof SequencerCall) {
+ SequencerCall that = (SequencerCall)obj;
+ if (!this.sequencerName.equals(that.sequencerName)) return false;
+ if (!this.sequencedPropertyName.equals(that.sequencedPropertyName))
return false;
+ return true;
+ }
+ return false;
+ }
+ }
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapter.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapter.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapter.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,253 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import java.io.InputStream;
+import java.util.Calendar;
+import java.util.Set;
+import javax.jcr.Node;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.jboss.dna.common.jcr.Path;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.repository.RepositoryI18n;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.RepositoryNodePath;
+import org.jboss.dna.spi.sequencers.StreamSequencer;
+
+/**
+ * An adapter class that wraps a {@link StreamSequencer} instance to be a {@link
Sequencer}.
+ * @author Randall Hauch
+ */
+public class StreamSequencerAdapter implements Sequencer {
+
+ private SequencerConfig configuration;
+ private final StreamSequencer streamSequencer;
+
+ public StreamSequencerAdapter( StreamSequencer streamSequencer ) {
+ this.streamSequencer = streamSequencer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public SequencerConfig getConfiguration() {
+ return this.configuration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setConfiguration( SequencerConfig configuration ) {
+ this.configuration = configuration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void execute( Node input, String sequencedPropertyName, NodeChange changes,
Set<RepositoryNodePath> outputPaths, ExecutionContext context, ProgressMonitor
progressMonitor )
+ throws RepositoryException, SequencerException {
+ // 'sequencedPropertyName' contains the name of the modified property on
'input' that resuled the call to this sequencer
+ // 'changes' contains all of the changes to this node that occurred in
the transaction.
+ // 'outputPaths' contains the paths of the node(s) where this sequencer
is to save it's data
+
+ try {
+ progressMonitor.beginTask(100, RepositoryI18n.sequencingPropertyOnNode,
sequencedPropertyName, input.getPath());
+
+ // Get the property that contains the image data, given by
'propertyName' ...
+ Property imageDataProperty = null;
+ try {
+ imageDataProperty = input.getProperty(sequencedPropertyName);
+ } catch (PathNotFoundException e) {
+ String msg =
RepositoryI18n.unableToFindPropertyForSequencing.text(sequencedPropertyName,
input.getPath());
+ throw new SequencerException(msg, e);
+ }
+ progressMonitor.worked(10);
+
+ // Get the binary property with the image content, and build the image
metadata from the image ...
+ SequencerOutputMap output = new SequencerOutputMap();
+ InputStream stream = null;
+ Throwable firstError = null;
+ ProgressMonitor sequencingMonitor = progressMonitor.createSubtask(50);
+ try {
+ stream = imageDataProperty.getStream();
+ this.streamSequencer.sequence(stream, output, sequencingMonitor);
+ } catch (Throwable t) {
+ // Record the error ...
+ firstError = t;
+ } finally {
+ sequencingMonitor.done();
+ if (stream != null) {
+ // Always close the stream, recording the error if we've not yet
seen an error
+ try {
+ stream.close();
+ } catch (Throwable t) {
+ if (firstError == null) firstError = t;
+ } finally {
+ stream = null;
+ }
+ }
+ if (firstError != null) {
+ // Wrap and throw the first error that we saw ...
+ throw new SequencerException(firstError);
+ }
+ }
+
+ // Find each output node and save the image metadata there ...
+ ProgressMonitor writingProgress = progressMonitor.createSubtask(40);
+ writingProgress.beginTask(outputPaths.size(),
RepositoryI18n.writingOutputSequencedFromPropertyOnNodes, sequencedPropertyName,
input.getPath(), outputPaths.size());
+ for (RepositoryNodePath outputPath : outputPaths) {
+ Session session = null;
+ try {
+ // Get the name of the repository workspace and the path to the
output node
+ final String repositoryWorkspaceName =
outputPath.getRepositoryWorkspaceName();
+ final String nodePath = outputPath.getNodePath();
+
+ // Create a session to the repository where the data should be
written ...
+ session =
context.getSessionFactory().createSession(repositoryWorkspaceName);
+
+ // Find or create the output node in this session ...
+ Node outputNode = context.getTools().findOrCreateNode(session,
nodePath);
+
+ // Now save the image metadata to the output node ...
+ if (saveOutput(outputNode, output, context)) {
+ session.save();
+ }
+ } finally {
+ writingProgress.worked(1);
+ // Always close the session ...
+ if (session != null) session.logout();
+ }
+ }
+ writingProgress.done();
+ } finally {
+ progressMonitor.done();
+ }
+ }
+
+ /**
+ * Save the sequencing output to the supplied node. This method does not need to save
the output, as that is done by the
+ * caller of this method.
+ * @param outputNode the existing node onto (or below) which the output is to be
written; never null
+ * @param outputProperties the (immutable) sequencing output; never null
+ * @param context the execution context for this sequencing operation; never null
+ * @return true if the output was written to the node, or false if no information was
written
+ * @throws RepositoryException
+ */
+ protected boolean saveOutput( Node outputNode, SequencerOutputMap output,
ExecutionContext context ) throws RepositoryException {
+ if (output.isEmpty()) return false;
+ final Path outputNodePath = new Path(outputNode.getPath());
+
+ // Iterate over the entries in the output, in Path's natural order (shorter
paths first and in lexicographical order by
+ // prefix and name)
+ for (SequencerOutputMap.Entry entry : output) {
+ Path targetNodePath = entry.getPath();
+ String primaryType = entry.getPrimaryTypeValue();
+
+ // Resolve this path relative to the output node path, handling any parent or
self references ...
+ Path absolutePath = targetNodePath.isAbsolute() ? targetNodePath :
outputNodePath.resolve(targetNodePath);
+ Path relativePath = absolutePath.relativeTo(outputNodePath);
+
+ // Find or add the node (which may involve adding intermediate nodes) ...
+ Node targetNode = outputNode;
+ for (int i = 0, max = relativePath.size(); i != max; ++i) {
+ Path.Segment segment = relativePath.getSegment(i);
+ String qualifiedName = segment.getQualifiedName(true);
+ if (targetNode.hasNode(qualifiedName)) {
+ targetNode = targetNode.getNode(qualifiedName);
+ } else {
+ // It doesn't exist, so create it ...
+ if (segment.hasIndex()) {
+ // Use a name without an index ...
+ qualifiedName = segment.getQualifiedName(false);
+ }
+ // We only have the primary type for the final one ...
+ if (i == (max - 1) && primaryType != null) {
+ targetNode = targetNode.addNode(qualifiedName, primaryType);
+ } else {
+ targetNode = targetNode.addNode(qualifiedName);
+ }
+ }
+ assert targetNode != null;
+ }
+ assert targetNode != null;
+
+ // Set all of the properties on this
+ for (SequencerOutputMap.PropertyValue property : entry.getPropertyValues())
{
+ String propertyName = property.getName();
+ Object value = property.getValue();
+ Logger.getLogger(this.getClass()).trace("Writing property
{0}/{1}={2}", targetNode.getPath(), propertyName, value);
+ if (value instanceof Boolean) {
+ targetNode.setProperty(propertyName,
((Boolean)value).booleanValue());
+ } else if (value instanceof String) {
+ targetNode.setProperty(propertyName, (String)value);
+ } else if (value instanceof Integer) {
+ targetNode.setProperty(propertyName, ((Integer)value).intValue());
+ } else if (value instanceof Short) {
+ targetNode.setProperty(propertyName, ((Short)value).shortValue());
+ } else if (value instanceof Long) {
+ targetNode.setProperty(propertyName, ((Long)value).longValue());
+ } else if (value instanceof Float) {
+ targetNode.setProperty(propertyName, ((Float)value).floatValue());
+ } else if (value instanceof Double) {
+ targetNode.setProperty(propertyName, ((Double)value).doubleValue());
+ } else if (value instanceof Calendar) {
+ targetNode.setProperty(propertyName, (Calendar)value);
+ } else if (value instanceof Path) {
+ // Find the path to reference node ...
+ Path pathToReferencedNode = (Path)value;
+ if (!pathToReferencedNode.isAbsolute()) {
+ // Resolve the path relative to the output node ...
+ pathToReferencedNode =
outputNodePath.resolve(pathToReferencedNode);
+ }
+ // Find the referenced node ...
+ try {
+ Node referencedNode =
outputNode.getNode(pathToReferencedNode.getString());
+ targetNode.setProperty(propertyName, referencedNode);
+ } catch (PathNotFoundException e) {
+ String msg =
RepositoryI18n.errorGettingNodeRelativeToNode.text(value, outputNode.getPath());
+ throw new SequencerException(msg, e);
+ }
+ } else if (value == null) {
+ // Remove the property ...
+ targetNode.setProperty(propertyName, (String)null);
+ } else {
+ String msg = RepositoryI18n.unknownPropertyValueType.text(value,
value.getClass().getName());
+ throw new SequencerException(msg);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ protected String[] extractMixinTypes( Object value ) {
+ if (value instanceof String[]) return (String[])value;
+ if (value instanceof String) return new String[] {(String)value};
+ return null;
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AbstractServiceAdministrator.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AbstractServiceAdministrator.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AbstractServiceAdministrator.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,216 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.services;
+
+import org.jboss.dna.repository.RepositoryI18n;
+import net.jcip.annotations.GuardedBy;
+import net.jcip.annotations.ThreadSafe;
+
+/**
+ * Simple abstract implementation of the service administrator interface that can be
easily subclassed by services that require an
+ * administrative interface.
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public abstract class AbstractServiceAdministrator implements ServiceAdministrator {
+
+ private volatile State state;
+
+ protected AbstractServiceAdministrator( State initialState ) {
+ assert initialState != null;
+ this.state = initialState;
+ }
+
+ /**
+ * Return the current state of this service.
+ * @return the current state
+ */
+ public State getState() {
+ return this.state;
+ }
+
+ /**
+ * Set the state of the service. This method does nothing if the desired state
matches the current state.
+ * @param state the desired state
+ * @return this object for method chaining purposes
+ * @see #setState(String)
+ * @see #start()
+ * @see #pause()
+ * @see #shutdown()
+ */
+ @GuardedBy( "this" )
+ public synchronized ServiceAdministrator setState( State state ) {
+ switch (state) {
+ case STARTED:
+ start();
+ break;
+ case PAUSED:
+ pause();
+ break;
+ case SHUTDOWN:
+ shutdown();
+ break;
+ }
+ return this;
+ }
+
+ /**
+ * Set the state of the service. This method does nothing if the desired state
matches the current state.
+ * @param state the desired state in string form
+ * @return this object for method chaining purposes
+ * @throws IllegalArgumentException if the specified state string is null or does not
match one of the predefined
+ * {@link ServiceAdministrator.State predefined enumerated values}
+ * @see #setState(State)
+ * @see #start()
+ * @see #pause()
+ * @see #shutdown()
+ */
+ public ServiceAdministrator setState( String state ) {
+ State newState = state == null ? null : State.valueOf(state.toUpperCase());
+ if (newState == null) {
+ throw new
IllegalArgumentException(RepositoryI18n.invalidStateString.text(state));
+ }
+ return setState(newState);
+ }
+
+ /**
+ * Start monitoring and sequence the events. This method can be called multiple
times, including after the service is
+ * {@link #pause() paused}. However, once the service is {@link #shutdown()
shutdown}, it cannot be started or paused.
+ * @return this object for method chaining purposes
+ * @throws IllegalStateException if called when the service has been {@link
#shutdown() shutdown}.
+ * @see #pause()
+ * @see #shutdown()
+ * @see #isStarted()
+ */
+ public synchronized ServiceAdministrator start() {
+ if (isShutdown()) throw new
IllegalStateException(RepositoryI18n.serviceShutdowAndMayNotBeStarted.text(serviceName()));
+ if (this.state != State.STARTED) {
+ doStart(this.state);
+ this.state = State.STARTED;
+ }
+ return this;
+ }
+
+ /**
+ * Implementation of the functionality to switch to the started state. This method is
only called if the state from which the
+ * service is transitioning is appropriate ({@link State#PAUSED}). This method does
nothing by default, and should be
+ * overridden if needed.
+ * @param fromState the state from which this service is transitioning; never null
+ * @throws IllegalStateException if the service is such that it cannot be
transitioned from the supplied state
+ */
+ @GuardedBy( "this" )
+ protected void doStart( State fromState ) {
+ }
+
+ /**
+ * Temporarily stop monitoring and sequencing events. This method can be called
multiple times, including after the service is
+ * {@link #start() started}. However, once the service is {@link #shutdown()
shutdown}, it cannot be started or paused.
+ * @return this object for method chaining purposes
+ * @throws IllegalStateException if called when the service has been {@link
#shutdown() shutdown}.
+ * @see #start()
+ * @see #shutdown()
+ * @see #isPaused()
+ */
+ public synchronized ServiceAdministrator pause() {
+ if (isShutdown()) throw new
IllegalStateException(RepositoryI18n.serviceShutdowAndMayNotBePaused.text(serviceName()));
+ if (this.state != State.PAUSED) {
+ doPause(this.state);
+ this.state = State.PAUSED;
+ }
+ return this;
+ }
+
+ /**
+ * Implementation of the functionality to switch to the paused state. This method is
only called if the state from which the
+ * service is transitioning is appropriate ({@link State#STARTED}). This method does
nothing by default, and should be
+ * overridden if needed.
+ * @param fromState the state from which this service is transitioning; never null
+ * @throws IllegalStateException if the service is such that it cannot be
transitioned from the supplied state
+ */
+ @GuardedBy( "this" )
+ protected void doPause( State fromState ) {
+ }
+
+ /**
+ * Permanently stop monitoring and sequencing events. This method can be called
multiple times, but only the first call has an
+ * effect. Once the service has been shutdown, it may not be {@link #start()
restarted} or {@link #pause() paused}.
+ * @return this object for method chaining purposes
+ * @see #start()
+ * @see #pause()
+ * @see #isShutdown()
+ */
+ public synchronized ServiceAdministrator shutdown() {
+ if (this.state != State.SHUTDOWN) {
+ doShutdown(this.state);
+ this.state = State.SHUTDOWN;
+ }
+ return this;
+ }
+
+ /**
+ * Implementation of the functionality to switch to the shutdown state. This method
is only called if the state from which the
+ * service is transitioning is appropriate ({@link State#STARTED} or {@link
State#PAUSED}). This method does nothing by
+ * default, and should be overridden if needed.
+ * @param fromState the state from which this service is transitioning; never null
+ * @throws IllegalStateException if the service is such that it cannot be
transitioned from the supplied state
+ */
+ @GuardedBy( "this" )
+ protected void doShutdown( State fromState ) {
+ }
+
+ /**
+ * Return whether this service has been started and is currently running.
+ * @return true if started and currently running, or false otherwise
+ * @see #start()
+ * @see #pause()
+ * @see #isPaused()
+ * @see #isShutdown()
+ */
+ public boolean isStarted() {
+ return this.state == State.STARTED;
+ }
+
+ /**
+ * Return whether this service is currently paused.
+ * @return true if currently paused, or false otherwise
+ * @see #pause()
+ * @see #start()
+ * @see #isStarted()
+ * @see #isShutdown()
+ */
+ public boolean isPaused() {
+ return this.state == State.PAUSED;
+ }
+
+ /**
+ * Return whether this service is stopped and unable to be restarted.
+ * @return true if currently shutdown, or false otherwise
+ * @see #shutdown()
+ * @see #isPaused()
+ * @see #isStarted()
+ */
+ public boolean isShutdown() {
+ return this.state == State.SHUTDOWN;
+ }
+
+ protected abstract String serviceName();
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AbstractServiceAdministrator.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AdministeredService.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AdministeredService.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AdministeredService.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,30 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.services;
+
+/**
+ * @author Randall Hauch
+ */
+public interface AdministeredService {
+
+ ServiceAdministrator getAdministrator();
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/AdministeredService.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/ServiceAdministrator.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/ServiceAdministrator.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/ServiceAdministrator.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,144 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.services;
+
+import java.util.concurrent.TimeUnit;
+import net.jcip.annotations.ThreadSafe;
+
+/**
+ * Contract defining an administrative interface for controlling the running state of a
service.
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public interface ServiceAdministrator {
+
+ /**
+ * The available states.
+ * @author Randall Hauch
+ */
+ public static enum State {
+ STARTED,
+ PAUSED,
+ SHUTDOWN;
+ }
+
+ /**
+ * Return the current state of this system.
+ * @return the current state
+ */
+ public State getState();
+
+ /**
+ * Set the state of the system. This method does nothing if the desired state matches
the current state.
+ * @param state the desired state
+ * @return this object for method chaining purposes
+ * @see #setState(String)
+ * @see #start()
+ * @see #pause()
+ * @see #shutdown()
+ */
+ public ServiceAdministrator setState( State state );
+
+ /**
+ * Set the state of the system. This method does nothing if the desired state matches
the current state.
+ * @param state the desired state in string form
+ * @return this object for method chaining purposes
+ * @throws IllegalArgumentException if the specified state string is null or does not
match one of the predefined
+ * {@link State predefined enumerated values}
+ * @see #setState(State)
+ * @see #start()
+ * @see #pause()
+ * @see #shutdown()
+ */
+ public ServiceAdministrator setState( String state );
+
+ /**
+ * Start monitoring and sequence the events. This method can be called multiple
times, including after the system is
+ * {@link #pause() paused}. However, once the system is {@link #shutdown() shutdown},
it cannot be started or paused.
+ * @return this object for method chaining purposes
+ * @throws IllegalStateException if called when the system has been {@link
#shutdown() shutdown}.
+ * @see #pause()
+ * @see #shutdown()
+ * @see #isStarted()
+ */
+ public ServiceAdministrator start();
+
+ /**
+ * Temporarily stop monitoring and sequencing events. This method can be called
multiple times, including after the system is
+ * {@link #start() started}. However, once the system is {@link #shutdown()
shutdown}, it cannot be started or paused.
+ * @return this object for method chaining purposes
+ * @throws IllegalStateException if called when the system has been {@link
#shutdown() shutdown}.
+ * @see #start()
+ * @see #shutdown()
+ * @see #isPaused()
+ */
+ public ServiceAdministrator pause();
+
+ /**
+ * Permanently stop monitoring and sequencing events. This method can be called
multiple times, but only the first call has an
+ * effect. Once the system has been shutdown, it may not be {@link #start()
restarted} or {@link #pause() paused}.
+ * @return this object for method chaining purposes
+ * @see #start()
+ * @see #pause()
+ * @see #isShutdown()
+ */
+ public ServiceAdministrator shutdown();
+
+ /**
+ * Blocks until all work has completed execution after a shutdown request, or the
timeout occurs, or the current thread is
+ * interrupted, whichever happens first.
+ * @param timeout the maximum time to wait
+ * @param unit the time unit of the timeout argument
+ * @return <tt>true</tt> if this service terminated and
<tt>false</tt> if the timeout elapsed before termination
+ * @throws InterruptedException if interrupted while waiting
+ */
+ boolean awaitTermination( long timeout, TimeUnit unit ) throws InterruptedException;
+
+ /**
+ * Return whether this system has been started and is currently running.
+ * @return true if started and currently running, or false otherwise
+ * @see #start()
+ * @see #pause()
+ * @see #isPaused()
+ * @see #isShutdown()
+ */
+ public boolean isStarted();
+
+ /**
+ * Return whether this system is currently paused.
+ * @return true if currently paused, or false otherwise
+ * @see #pause()
+ * @see #start()
+ * @see #isStarted()
+ * @see #isShutdown()
+ */
+ public boolean isPaused();
+
+ /**
+ * Return whether this system is stopped and unable to be restarted.
+ * @return true if currently shutdown, or false otherwise
+ * @see #shutdown()
+ * @see #isPaused()
+ * @see #isStarted()
+ */
+ public boolean isShutdown();
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/services/ServiceAdministrator.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/ExecutionContext.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/ExecutionContext.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/ExecutionContext.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,45 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.util;
+
+import javax.jcr.Session;
+
+/**
+ * The context of an execution.
+ * @author Randall Hauch
+ */
+public interface ExecutionContext {
+
+ /**
+ * Get the session factory, which can be used to obtain sessions temporarily for this
context. Any session obtained from this
+ * factory should be {@link Session#logout() closed} before the execution finishes.
+ * @return the session factory
+ */
+ SessionFactory getSessionFactory();
+
+ /**
+ * Get a set of utilities for working with JCR.
+ * @return the tools
+ */
+ JcrTools getTools();
+
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/ExecutionContext.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added: trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JcrTools.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JcrTools.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JcrTools.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,300 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFormatException;
+import org.jboss.dna.common.collection.Problem;
+import org.jboss.dna.common.collection.Problems;
+import org.jboss.dna.common.util.IoUtil;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.common.util.StringUtil;
+import org.jboss.dna.repository.RepositoryI18n;
+
+/**
+ * @author Randall Hauch
+ */
+public class JcrTools {
+
+ public Map<String, Object> loadProperties( Node propertyContainer, Problems
problems ) {
+ return loadProperties(propertyContainer, null, problems);
+ }
+
+ public Map<String, Object> loadProperties( Node propertyContainer,
Map<String, Object> properties, Problems problems ) {
+ if (properties == null) properties = new HashMap<String, Object>();
+ if (propertyContainer != null) {
+ try {
+ NodeIterator iter = propertyContainer.getNodes();
+ while (iter.hasNext()) {
+ Node propertyNode = iter.nextNode();
+ if (propertyNode != null &&
propertyNode.getPrimaryNodeType().isNodeType("dna:property")) {
+ String propertyName = propertyNode.getName();
+ Object propertyValue = getPropertyValue(propertyNode,
"dna:propertyValue", true, problems);
+ properties.put(propertyName, propertyValue);
+ }
+ }
+ } catch (RepositoryException e) {
+ problems.addError(e,
RepositoryI18n.errorReadingPropertiesFromContainerNode, getReadable(propertyContainer));
+ }
+ }
+
+ return properties;
+ }
+
+ public boolean removeProblems( Node parent ) throws RepositoryException {
+ Node problemsNode = null;
+ if (parent.hasNode("dna:problems")) {
+ problemsNode = parent.getNode("dna:problems");
+ problemsNode.remove();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean storeProblems( Node parent, Problems problems ) throws
RepositoryException {
+ Node problemsNode = null;
+ if (parent.hasNode("dna:problems")) {
+ problemsNode = parent.getNode("dna:problems");
+ // Delete all problems ...
+ removeAllChildren(problemsNode);
+ }
+ if (problems.isEmpty()) {
+ return false;
+ }
+ if (problemsNode == null) {
+ problemsNode = parent.addNode("dna:problems"); // primary type
dictated by child definition
+ }
+
+ // Add a child for each problem ...
+ for (Problem problem : problems) {
+ Node problemNode = problemsNode.addNode("problem",
"dna:problem");
+ // - dna:status (string) mandatory copy
+ // < 'ERROR', 'WARNING', 'INFO'
+ // - dna:message (string) mandatory copy
+ // - dna:code (string) copy
+ // - dna:type (string) copy
+ // - dna:resource (string) copy
+ // - dna:location (string) copy
+ // - dna:trace (string) copy
+ problemNode.setProperty("dna:status", problem.getStatus().name());
+ problemNode.setProperty("dna:message",
problem.getMessageString());
+ if (problem.getCode() != Problem.DEFAULT_CODE) {
+ problemNode.setProperty("dna:code",
Integer.toString(problem.getCode()));
+ }
+ String resource = problem.getResource();
+ if (resource != null) {
+ problemNode.setProperty("dna:resource", resource);
+ }
+ String location = problem.getLocation();
+ if (location != null) {
+ problemNode.setProperty("dna:location", location);
+ }
+ Throwable t = problem.getThrowable();
+ if (t != null) {
+ String trace = StringUtil.getStackTrace(t);
+ problemNode.setProperty("dna:trace", trace);
+ }
+ }
+ return true;
+ }
+
+ public int removeAllChildren( Node node ) throws RepositoryException {
+ int childrenRemoved = 0;
+ NodeIterator iter = node.getNodes();
+ while (iter.hasNext()) {
+ Node child = iter.nextNode();
+ child.remove();
+ ++childrenRemoved;
+ }
+ return childrenRemoved;
+ }
+
+ public String getPropertyAsString( Node node, String propertyName, boolean required,
Problems problems ) {
+ return getPropertyAsString(node, propertyName, required, null);
+ }
+
+ public String getPropertyAsString( Node node, String propertyName, boolean required,
String defaultValue, Problems problems ) {
+ try {
+ Property property = node.getProperty(propertyName);
+ return property.getString();
+ } catch (ValueFormatException e) {
+ if (required) {
+ problems.addError(e,
RepositoryI18n.requiredPropertyOnNodeWasExpectedToBeStringValue, propertyName,
getReadable(node));
+ } else {
+ problems.addError(e,
RepositoryI18n.optionalPropertyOnNodeWasExpectedToBeStringValue, propertyName,
getReadable(node));
+ }
+ } catch (PathNotFoundException e) {
+ if (required) {
+ problems.addError(e, RepositoryI18n.requiredPropertyIsMissingFromNode,
propertyName, getReadable(node));
+ }
+ if (!required) return defaultValue;
+ } catch (RepositoryException err) {
+ if (required) {
+ problems.addError(err,
RepositoryI18n.errorGettingRequiredPropertyFromNode, propertyName, getReadable(node));
+ } else {
+ problems.addError(err,
RepositoryI18n.errorGettingOptionalPropertyFromNode, propertyName, getReadable(node));
+ }
+ }
+ return null;
+ }
+
+ public Object getPropertyValue( Node node, String propertyName, boolean required,
Problems problems ) {
+ try {
+ Property property = node.getProperty(propertyName);
+ switch (property.getType()) {
+ case PropertyType.BINARY: {
+ InputStream stream = property.getStream();
+ try {
+ stream = property.getStream();
+ return IoUtil.readBytes(stream);
+ } finally {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (IOException e) {
+ // Log ...
+ Logger.getLogger(this.getClass()).error(e,
RepositoryI18n.errorClosingBinaryStreamForPropertyFromNode, propertyName,
node.getPath());
+ }
+ }
+ }
+ }
+ default: {
+ return property.getString();
+ }
+ }
+ } catch (IOException e) {
+ if (required) {
+ problems.addError(e, RepositoryI18n.requiredPropertyOnNodeCouldNotBeRead,
propertyName, getReadable(node));
+ } else {
+ problems.addError(e, RepositoryI18n.optionalPropertyOnNodeCouldNotBeRead,
propertyName, getReadable(node));
+ }
+ } catch (PathNotFoundException e) {
+ if (required) {
+ problems.addError(e, RepositoryI18n.requiredPropertyIsMissingFromNode,
propertyName, getReadable(node));
+ }
+ } catch (RepositoryException err) {
+ if (required) {
+ problems.addError(err,
RepositoryI18n.errorGettingRequiredPropertyFromNode, propertyName, getReadable(node));
+ } else {
+ problems.addError(err,
RepositoryI18n.errorGettingOptionalPropertyFromNode, propertyName, getReadable(node));
+ }
+ }
+ return null;
+ }
+
+ public String[] getPropertyAsStringArray( Node node, String propertyName, boolean
required, Problems problems, String... defaultValues ) {
+ String[] result = defaultValues;
+ try {
+ Property property = node.getProperty(propertyName);
+ if (property.getDefinition().isMultiple()) {
+ Value[] values = property.getValues();
+ result = new String[values.length];
+ int i = 0;
+ for (Value value : values) {
+ result[i++] = value.getString();
+ }
+ } else {
+ result = new String[] {property.getString()};
+ }
+ } catch (ValueFormatException e) {
+ if (required) {
+ problems.addError(e,
RepositoryI18n.requiredPropertyOnNodeWasExpectedToBeStringArrayValue, propertyName,
getReadable(node));
+ } else {
+ problems.addError(e,
RepositoryI18n.optionalPropertyOnNodeWasExpectedToBeStringArrayValue, propertyName,
getReadable(node));
+ }
+ } catch (PathNotFoundException e) {
+ if (required) {
+ problems.addError(e, RepositoryI18n.requiredPropertyIsMissingFromNode,
propertyName, getReadable(node));
+ }
+ } catch (RepositoryException err) {
+ if (required) {
+ problems.addError(err,
RepositoryI18n.errorGettingRequiredPropertyFromNode, propertyName, getReadable(node));
+ } else {
+ problems.addError(err,
RepositoryI18n.errorGettingOptionalPropertyFromNode, propertyName, getReadable(node));
+ }
+ }
+ return result;
+ }
+
+ public Node getNode( Node node, String relativePath, boolean required, Problems
problems ) {
+ Node result = null;
+ try {
+ result = node.getNode(relativePath);
+ } catch (PathNotFoundException e) {
+ if (required) problems.addError(e,
RepositoryI18n.requiredNodeDoesNotExistRelativeToNode, relativePath, getReadable(node));
+ } catch (RepositoryException err) {
+ problems.addError(err, RepositoryI18n.errorGettingNodeRelativeToNode,
relativePath, getReadable(node));
+ }
+ return result;
+ }
+
+ public String getReadable( Node node ) {
+ if (node == null) return "";
+ try {
+ return node.getPath();
+ } catch (RepositoryException err) {
+ return node.toString();
+ }
+ }
+
+ public Node findOrCreateNode( Session session, String path ) throws
RepositoryException {
+ Node root = session.getRootNode();
+ // Remove leading and trailing slashes ...
+ String relPath = path.replaceAll("^/+",
"").replaceAll("/+$", "");
+
+ // Look for the node first ...
+ try {
+ return root.getNode(relPath);
+ } catch (PathNotFoundException e) {
+ // continue
+ }
+ // Create the node, which has to be done segment by segment ...
+ String[] pathSegments = relPath.split("/");
+ Node node = root;
+ for (String pathSegment : pathSegments) {
+ pathSegment = pathSegment.trim();
+ if (pathSegment.length() == 0) continue;
+ if (node.hasNode(pathSegment)) {
+ // Find the existing node ...
+ node = node.getNode(pathSegment);
+ } else {
+ // Make sure there is no index on the final segment ...
+ String pathSegmentWithNoIndex =
pathSegment.replaceAll("(\\[\\d+\\])+$", "");
+ // Create the node ...
+ node = node.addNode(pathSegmentWithNoIndex);
+ }
+ }
+ return node;
+ }
+
+}
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JndiSessionFactory.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JndiSessionFactory.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JndiSessionFactory.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,249 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.repository.util;
+
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.jcr.Credentials;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import org.jboss.dna.common.SystemFailureException;
+import org.jboss.dna.common.util.ArgCheck;
+import org.jboss.dna.repository.RepositoryI18n;
+
+/**
+ * A SessionFactory implementation that creates {@link Session} instances using {@link
Repository} instances registered in JNDI.
+ * <p>
+ * This factory using a naming convention where the name supplied to the {@link
#createSession(String)} contains both the name of
+ * the repository and the name of the workspace. Typically, this is
<i><code>repositoryName/workspaceName</code></i>, where
+ * <code>repositoryName</code> is the JNDI name under which the Repository
instance was bound, and <code>workspaceName</code>
+ * is the name of the workspace. Note that this method looks for the last delimiter in
the whole name to distinguish between the
+ * repository and workspace names.
+ * </p>
+ * <p>
+ * For example, if
"<code>java:comp/env/repository/dataRepository/myWorkspace</code>"
is passed to the
+ * {@link #createSession(String)} method, this factory will look for a {@link Repository}
instance registered in JDNI with the
+ * name "<code>java:comp/env/repository/dataRepository</code>" and
use it to {@link Repository#login(String) create a session}
+ * to the workspace named "<code>myWorkspace</code>".
+ * </p>
+ * <p>
+ * By default, this factory creates an anonymous JCR session. To use sessions with
specific {@link Credentials}, simply
+ * {@link #registerCredentials(String, Credentials) register} credentials for the
appropriate repository/workspace name. For
+ * security reasons, it is not possible to retrieve the Credentials once registered with
this factory.
+ * </p>
+ * @author Randall Hauch
+ */
+public class JndiSessionFactory implements SessionFactory {
+
+ protected static char[] DEFAULT_DELIMITERS = new char[] {'/'};
+
+ private final char[] workspaceDelims;
+ private final String workspaceDelimsRegexCharacterSet;
+ private final Context context;
+ private final Map<String, Credentials> credentials = new
ConcurrentHashMap<String, Credentials>();
+
+ /**
+ * Create an instance of the factory by creating a new {@link InitialContext}. This
is equivalent to calling
+ * <code>new JndiSessionFactory(new InitialContext())</code>.
+ * @throws NamingException if there is a problem creating the InitialContext.
+ */
+ public JndiSessionFactory() throws NamingException {
+ this(new InitialContext());
+ }
+
+ /**
+ * Create an instance of the factory by supplying the characters that may be used to
delimit the workspace name from the
+ * repository name. This constructor initializes the factory with a new {@link
InitialContext}, and is equivalent to calling
+ * <code>new JndiSessionFactory(new
InitialContext(),workspaceDelimiters)</code>.
+ * @param workspaceDelimiters the delimiters, or null/empty if the default delimiter
of '/' should be used.
+ * @throws NamingException if there is a problem creating the InitialContext.
+ */
+ public JndiSessionFactory( char... workspaceDelimiters ) throws NamingException {
+ this(new InitialContext(), workspaceDelimiters);
+ }
+
+ /**
+ * Create an instance of the factory using the supplied JNDI context.
+ * @param context the naming context
+ * @throws IllegalArgumentException if the context parameter is null
+ */
+ public JndiSessionFactory( Context context ) {
+ this(context, DEFAULT_DELIMITERS);
+ }
+
+ /**
+ * Create an instance of the factory by supplying naming context and the characters
that may be used to delimit the workspace
+ * name from the repository name.
+ * @param context the naming context
+ * @param workspaceDelimiters the delimiters, or null/empty if the default delimiter
of '/' should be used.
+ * @throws IllegalArgumentException if the context parameter is null
+ */
+ public JndiSessionFactory( Context context, char... workspaceDelimiters ) {
+ ArgCheck.isNotNull(context, "initial context");
+ this.context = context;
+ this.workspaceDelims = (workspaceDelimiters == null || workspaceDelimiters.length
== 0) ? DEFAULT_DELIMITERS : workspaceDelimiters;
+ StringBuilder sb = new StringBuilder();
+ for (char delim : this.workspaceDelims) {
+ switch (delim) {
+ case '\\':
+ sb.append("\\");
+ break;
+ // case '[' : sb.append("\\["); break;
+ case ']':
+ sb.append("\\]");
+ break;
+ case '-':
+ sb.append("\\-");
+ break;
+ case '^':
+ sb.append("\\^");
+ break;
+ default:
+ sb.append(delim);
+ }
+ }
+ this.workspaceDelimsRegexCharacterSet = sb.toString();
+ }
+
+ /**
+ * Convenience method to bind a repository in JNDI. Repository instances can be bound
into JNDI using any technique, so this
+ * method need not be used. <i>Note that the name should not contain the
workspace part.</i>
+ * @param name the name of the repository, without the workspace name component.
+ * @param repository the repository to be bound, or null if an existing repository
should be unbound.
+ * @throws NamingException if there is a problem registering the supplied repository
+ */
+ public void registerRepository( String name, Repository repository ) throws
NamingException {
+ assert name != null;
+ // Remove all trailing delimiters ...
+ name = name.replaceAll("[" + this.workspaceDelimsRegexCharacterSet +
"]+$", "");
+ if (repository != null) {
+ this.context.bind(name, repository);
+ } else {
+ this.context.unbind(name);
+ }
+ }
+
+ /**
+ * Register the credentials for the repository and workspace given by the supplied
name, username and password. This is
+ * equivalent to calling <code>registerCredentials(name, new
SimpleCredentials(username,password))</code>, although if
+ * <code>username</code> is null then this is equivalent to
<code>registerCredentials(name,null)</code>.
+ * @param name the name of the repository and workspace
+ * @param username the username to use, or null if the existing credentials for the
named workspace should be removed
+ * @param password the password, may be null or empty
+ * @return true if this overwrote existing credentials
+ * @see #registerCredentials(String, Credentials)
+ * @see #removeCredentials(String)
+ */
+ public boolean registerCredentials( String name, String username, char[] password )
{
+ if (password == null && username != null) password = new char[] {};
+ Credentials creds = username == null ? null : new SimpleCredentials(username,
password);
+ return registerCredentials(name, creds);
+ }
+
+ /**
+ * Register the credentials to be used for the named repository and workspace. Use
the same name as used to
+ * {@link #createSession(String) create sessions}.
+ * @param name the name of the repository and workspace
+ * @param credentials the credentials to use, or null if the existing credentials for
the named workspace should be removed
+ * @return true if this overwrote existing credentials
+ * @see #registerCredentials(String, String, char[])
+ * @see #removeCredentials(String)
+ */
+ public boolean registerCredentials( String name, Credentials credentials ) {
+ boolean foundExisting = false;
+ name = name != null ? name.trim() : null;
+ if (credentials == null) {
+ foundExisting = this.credentials.remove(name) != null;
+ } else {
+ foundExisting = this.credentials.put(name, credentials) != null;
+ }
+ return foundExisting;
+ }
+
+ /**
+ * Remove any credentials associated with the named repository and workspace. This is
equivalent to calling
+ * <code>registerCredentials(name,null)</code>.
+ * @param name the name of the repository and workspace
+ * @return true if existing credentials were found and removed, or false if no such
credentials existed
+ * @see #registerCredentials(String, Credentials)
+ */
+ public boolean removeCredentials( String name ) {
+ return registerCredentials(name, null);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Session createSession( String name ) throws RepositoryException {
+ ArgCheck.isNotNull(name, "session name");
+ name = name.trim();
+ // Look up the Repository object in JNDI ...
+ String repositoryName = getRepositoryName(name);
+ Repository repository = null;
+ try {
+ repository = (Repository)this.context.lookup(repositoryName);
+ } catch (NamingException err) {
+ throw new
SystemFailureException(RepositoryI18n.unableToFindRepositoryInJndi.text(repositoryName));
+ }
+
+ // Determine the name of the workspace ...
+ String workspaceName = getWorkspaceName(name);
+
+ // Look up any credentials, which may be null ...
+ Credentials creds = this.credentials.get(name);
+
+ // Create a session to the specified workspace and using the credentials (either
or both may be null) ...
+ return repository.login(creds, workspaceName);
+ }
+
+ protected String getWorkspaceName( String name ) {
+ assert name != null;
+ int index = getIndexOfLastWorkspaceDelimiter(name);
+ if (index == -1) return null;
+ if ((index + 1) == name.length()) return null; // delim is the last character
+ return name.substring(index + 1);
+ }
+
+ protected String getRepositoryName( String name ) {
+ assert name != null;
+ int index = getIndexOfLastWorkspaceDelimiter(name);
+ if (index == -1) return name; // no delim
+ if ((index + 1) == name.length()) return name.substring(0, index); // delim as
last character
+ return name.substring(0, index);
+ }
+
+ protected int getIndexOfLastWorkspaceDelimiter( String name ) {
+ int index = -1;
+ for (char delim : this.workspaceDelims) {
+ int i = name.lastIndexOf(delim);
+ index = Math.max(index, i);
+ }
+ return index;
+ }
+
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/JndiSessionFactory.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/RepositoryNodePath.java
===================================================================
---
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/RepositoryNodePath.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/RepositoryNodePath.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,105 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.util;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.repository.RepositoryI18n;
+
+/**
+ * @author Randall Hauch
+ */
+@Immutable
+public class RepositoryNodePath {
+
+ protected static final Pattern PATTERN = Pattern.compile("([^:/]):(/.*)");
+
+ public static RepositoryNodePath parse( String path, String
defaultRepositoryWorkspaceName ) {
+ Matcher matcher = PATTERN.matcher(path);
+ if (matcher.matches()) {
+ try {
+ return new RepositoryNodePath(matcher.group(1), matcher.group(2));
+ } catch (Throwable t) {
+ throw new
IllegalArgumentException(RepositoryI18n.invalidRepositoryNodePath.text(path,
t.getMessage()));
+ }
+ }
+ return new RepositoryNodePath(defaultRepositoryWorkspaceName, path);
+
+ }
+
+ private final String repositoryName;
+ private final String nodePath;
+ private final int hc;
+
+ public RepositoryNodePath( String repositoryName, String nodePath ) {
+ this.repositoryName = repositoryName;
+ this.nodePath = nodePath;
+ this.hc = HashCode.compute(this.repositoryName, this.nodePath);
+ }
+
+ /**
+ * @return nodePath
+ */
+ public String getNodePath() {
+ return this.nodePath;
+ }
+
+ /**
+ * @return repositoryName
+ */
+ public String getRepositoryWorkspaceName() {
+ return this.repositoryName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int hashCode() {
+ return this.hc;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (obj instanceof RepositoryNodePath) {
+ RepositoryNodePath that = (RepositoryNodePath)obj;
+ if (!this.repositoryName.equals(that.repositoryName)) return false;
+ if (!this.nodePath.equals(that.nodePath)) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return this.repositoryName + ":" + this.nodePath;
+ }
+}
Property changes on:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/RepositoryNodePath.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/SessionFactory.java
===================================================================
--- trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/SessionFactory.java
(rev 0)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/repository/util/SessionFactory.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,35 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.repository.util;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+/**
+ * @author Randall Hauch
+ */
+public interface SessionFactory {
+
+ Session createSession( String name ) throws RepositoryException;
+
+}
Deleted:
trunk/dna-repository/src/main/java/org/jboss/dna/services/AbstractServiceAdministrator.java
===================================================================
---
trunk/dna-services/src/main/java/org/jboss/dna/services/AbstractServiceAdministrator.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/AbstractServiceAdministrator.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,215 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import net.jcip.annotations.GuardedBy;
-import net.jcip.annotations.ThreadSafe;
-
-/**
- * Simple abstract implementation of the service administrator interface that can be
easily subclassed by services that require an
- * administrative interface.
- * @author Randall Hauch
- */
-@ThreadSafe
-public abstract class AbstractServiceAdministrator implements ServiceAdministrator {
-
- private volatile State state;
-
- protected AbstractServiceAdministrator( State initialState ) {
- assert initialState != null;
- this.state = initialState;
- }
-
- /**
- * Return the current state of this service.
- * @return the current state
- */
- public State getState() {
- return this.state;
- }
-
- /**
- * Set the state of the service. This method does nothing if the desired state
matches the current state.
- * @param state the desired state
- * @return this object for method chaining purposes
- * @see #setState(String)
- * @see #start()
- * @see #pause()
- * @see #shutdown()
- */
- @GuardedBy( "this" )
- public synchronized ServiceAdministrator setState( State state ) {
- switch (state) {
- case STARTED:
- start();
- break;
- case PAUSED:
- pause();
- break;
- case SHUTDOWN:
- shutdown();
- break;
- }
- return this;
- }
-
- /**
- * Set the state of the service. This method does nothing if the desired state
matches the current state.
- * @param state the desired state in string form
- * @return this object for method chaining purposes
- * @throws IllegalArgumentException if the specified state string is null or does not
match one of the predefined
- * {@link ServiceAdministrator.State predefined enumerated values}
- * @see #setState(State)
- * @see #start()
- * @see #pause()
- * @see #shutdown()
- */
- public ServiceAdministrator setState( String state ) {
- State newState = state == null ? null : State.valueOf(state.toUpperCase());
- if (newState == null) {
- throw new
IllegalArgumentException(ServicesI18n.invalidStateString.text(state));
- }
- return setState(newState);
- }
-
- /**
- * Start monitoring and sequence the events. This method can be called multiple
times, including after the service is
- * {@link #pause() paused}. However, once the service is {@link #shutdown()
shutdown}, it cannot be started or paused.
- * @return this object for method chaining purposes
- * @throws IllegalStateException if called when the service has been {@link
#shutdown() shutdown}.
- * @see #pause()
- * @see #shutdown()
- * @see #isStarted()
- */
- public synchronized ServiceAdministrator start() {
- if (isShutdown()) throw new
IllegalStateException(ServicesI18n.serviceShutdowAndMayNotBeStarted.text(serviceName()));
- if (this.state != State.STARTED) {
- doStart(this.state);
- this.state = State.STARTED;
- }
- return this;
- }
-
- /**
- * Implementation of the functionality to switch to the started state. This method is
only called if the state from which the
- * service is transitioning is appropriate ({@link State#PAUSED}). This method does
nothing by default, and should be
- * overridden if needed.
- * @param fromState the state from which this service is transitioning; never null
- * @throws IllegalStateException if the service is such that it cannot be
transitioned from the supplied state
- */
- @GuardedBy( "this" )
- protected void doStart( State fromState ) {
- }
-
- /**
- * Temporarily stop monitoring and sequencing events. This method can be called
multiple times, including after the service is
- * {@link #start() started}. However, once the service is {@link #shutdown()
shutdown}, it cannot be started or paused.
- * @return this object for method chaining purposes
- * @throws IllegalStateException if called when the service has been {@link
#shutdown() shutdown}.
- * @see #start()
- * @see #shutdown()
- * @see #isPaused()
- */
- public synchronized ServiceAdministrator pause() {
- if (isShutdown()) throw new
IllegalStateException(ServicesI18n.serviceShutdowAndMayNotBePaused.text(serviceName()));
- if (this.state != State.PAUSED) {
- doPause(this.state);
- this.state = State.PAUSED;
- }
- return this;
- }
-
- /**
- * Implementation of the functionality to switch to the paused state. This method is
only called if the state from which the
- * service is transitioning is appropriate ({@link State#STARTED}). This method does
nothing by default, and should be
- * overridden if needed.
- * @param fromState the state from which this service is transitioning; never null
- * @throws IllegalStateException if the service is such that it cannot be
transitioned from the supplied state
- */
- @GuardedBy( "this" )
- protected void doPause( State fromState ) {
- }
-
- /**
- * Permanently stop monitoring and sequencing events. This method can be called
multiple times, but only the first call has an
- * effect. Once the service has been shutdown, it may not be {@link #start()
restarted} or {@link #pause() paused}.
- * @return this object for method chaining purposes
- * @see #start()
- * @see #pause()
- * @see #isShutdown()
- */
- public synchronized ServiceAdministrator shutdown() {
- if (this.state != State.SHUTDOWN) {
- doShutdown(this.state);
- this.state = State.SHUTDOWN;
- }
- return this;
- }
-
- /**
- * Implementation of the functionality to switch to the shutdown state. This method
is only called if the state from which the
- * service is transitioning is appropriate ({@link State#STARTED} or {@link
State#PAUSED}). This method does nothing by
- * default, and should be overridden if needed.
- * @param fromState the state from which this service is transitioning; never null
- * @throws IllegalStateException if the service is such that it cannot be
transitioned from the supplied state
- */
- @GuardedBy( "this" )
- protected void doShutdown( State fromState ) {
- }
-
- /**
- * Return whether this service has been started and is currently running.
- * @return true if started and currently running, or false otherwise
- * @see #start()
- * @see #pause()
- * @see #isPaused()
- * @see #isShutdown()
- */
- public boolean isStarted() {
- return this.state == State.STARTED;
- }
-
- /**
- * Return whether this service is currently paused.
- * @return true if currently paused, or false otherwise
- * @see #pause()
- * @see #start()
- * @see #isStarted()
- * @see #isShutdown()
- */
- public boolean isPaused() {
- return this.state == State.PAUSED;
- }
-
- /**
- * Return whether this service is stopped and unable to be restarted.
- * @return true if currently shutdown, or false otherwise
- * @see #shutdown()
- * @see #isPaused()
- * @see #isStarted()
- */
- public boolean isShutdown() {
- return this.state == State.SHUTDOWN;
- }
-
- protected abstract String serviceName();
-}
Deleted:
trunk/dna-repository/src/main/java/org/jboss/dna/services/AdministeredService.java
===================================================================
---
trunk/dna-services/src/main/java/org/jboss/dna/services/AdministeredService.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/AdministeredService.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,30 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-/**
- * @author Randall Hauch
- */
-public interface AdministeredService {
-
- ServiceAdministrator getAdministrator();
-}
Deleted: trunk/dna-repository/src/main/java/org/jboss/dna/services/ExecutionContext.java
===================================================================
---
trunk/dna-services/src/main/java/org/jboss/dna/services/ExecutionContext.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/ExecutionContext.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,46 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import javax.jcr.Session;
-import org.jboss.dna.services.util.JcrTools;
-
-/**
- * The context of an execution.
- * @author Randall Hauch
- */
-public interface ExecutionContext {
-
- /**
- * Get the session factory, which can be used to obtain sessions temporarily for this
context. Any session obtained from this
- * factory should be {@link Session#logout() closed} before the execution finishes.
- * @return the session factory
- */
- SessionFactory getSessionFactory();
-
- /**
- * Get a set of utilities for working with JCR.
- * @return the tools
- */
- JcrTools getTools();
-
-}
Deleted:
trunk/dna-repository/src/main/java/org/jboss/dna/services/JndiSessionFactory.java
===================================================================
---
trunk/dna-services/src/main/java/org/jboss/dna/services/JndiSessionFactory.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/JndiSessionFactory.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,248 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-
-package org.jboss.dna.services;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import javax.jcr.Credentials;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-import javax.naming.Context;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import org.jboss.dna.common.SystemFailureException;
-import org.jboss.dna.common.util.ArgCheck;
-
-/**
- * A SessionFactory implementation that creates {@link Session} instances using {@link
Repository} instances registered in JNDI.
- * <p>
- * This factory using a naming convention where the name supplied to the {@link
#createSession(String)} contains both the name of
- * the repository and the name of the workspace. Typically, this is
<i><code>repositoryName/workspaceName</code></i>, where
- * <code>repositoryName</code> is the JNDI name under which the Repository
instance was bound, and <code>workspaceName</code>
- * is the name of the workspace. Note that this method looks for the last delimiter in
the whole name to distinguish between the
- * repository and workspace names.
- * </p>
- * <p>
- * For example, if
"<code>java:comp/env/repository/dataRepository/myWorkspace</code>"
is passed to the
- * {@link #createSession(String)} method, this factory will look for a {@link Repository}
instance registered in JDNI with the
- * name "<code>java:comp/env/repository/dataRepository</code>" and
use it to {@link Repository#login(String) create a session}
- * to the workspace named "<code>myWorkspace</code>".
- * </p>
- * <p>
- * By default, this factory creates an anonymous JCR session. To use sessions with
specific {@link Credentials}, simply
- * {@link #registerCredentials(String, Credentials) register} credentials for the
appropriate repository/workspace name. For
- * security reasons, it is not possible to retrieve the Credentials once registered with
this factory.
- * </p>
- * @author Randall Hauch
- */
-public class JndiSessionFactory implements SessionFactory {
-
- protected static char[] DEFAULT_DELIMITERS = new char[] {'/'};
-
- private final char[] workspaceDelims;
- private final String workspaceDelimsRegexCharacterSet;
- private final Context context;
- private final Map<String, Credentials> credentials = new
ConcurrentHashMap<String, Credentials>();
-
- /**
- * Create an instance of the factory by creating a new {@link InitialContext}. This
is equivalent to calling
- * <code>new JndiSessionFactory(new InitialContext())</code>.
- * @throws NamingException if there is a problem creating the InitialContext.
- */
- public JndiSessionFactory() throws NamingException {
- this(new InitialContext());
- }
-
- /**
- * Create an instance of the factory by supplying the characters that may be used to
delimit the workspace name from the
- * repository name. This constructor initializes the factory with a new {@link
InitialContext}, and is equivalent to calling
- * <code>new JndiSessionFactory(new
InitialContext(),workspaceDelimiters)</code>.
- * @param workspaceDelimiters the delimiters, or null/empty if the default delimiter
of '/' should be used.
- * @throws NamingException if there is a problem creating the InitialContext.
- */
- public JndiSessionFactory( char... workspaceDelimiters ) throws NamingException {
- this(new InitialContext(), workspaceDelimiters);
- }
-
- /**
- * Create an instance of the factory using the supplied JNDI context.
- * @param context the naming context
- * @throws IllegalArgumentException if the context parameter is null
- */
- public JndiSessionFactory( Context context ) {
- this(context, DEFAULT_DELIMITERS);
- }
-
- /**
- * Create an instance of the factory by supplying naming context and the characters
that may be used to delimit the workspace
- * name from the repository name.
- * @param context the naming context
- * @param workspaceDelimiters the delimiters, or null/empty if the default delimiter
of '/' should be used.
- * @throws IllegalArgumentException if the context parameter is null
- */
- public JndiSessionFactory( Context context, char... workspaceDelimiters ) {
- ArgCheck.isNotNull(context, "initial context");
- this.context = context;
- this.workspaceDelims = (workspaceDelimiters == null || workspaceDelimiters.length
== 0) ? DEFAULT_DELIMITERS : workspaceDelimiters;
- StringBuilder sb = new StringBuilder();
- for (char delim : this.workspaceDelims) {
- switch (delim) {
- case '\\':
- sb.append("\\");
- break;
- // case '[' : sb.append("\\["); break;
- case ']':
- sb.append("\\]");
- break;
- case '-':
- sb.append("\\-");
- break;
- case '^':
- sb.append("\\^");
- break;
- default:
- sb.append(delim);
- }
- }
- this.workspaceDelimsRegexCharacterSet = sb.toString();
- }
-
- /**
- * Convenience method to bind a repository in JNDI. Repository instances can be bound
into JNDI using any technique, so this
- * method need not be used. <i>Note that the name should not contain the
workspace part.</i>
- * @param name the name of the repository, without the workspace name component.
- * @param repository the repository to be bound, or null if an existing repository
should be unbound.
- * @throws NamingException if there is a problem registering the supplied repository
- */
- public void registerRepository( String name, Repository repository ) throws
NamingException {
- assert name != null;
- // Remove all trailing delimiters ...
- name = name.replaceAll("[" + this.workspaceDelimsRegexCharacterSet +
"]+$", "");
- if (repository != null) {
- this.context.bind(name, repository);
- } else {
- this.context.unbind(name);
- }
- }
-
- /**
- * Register the credentials for the repository and workspace given by the supplied
name, username and password. This is
- * equivalent to calling <code>registerCredentials(name, new
SimpleCredentials(username,password))</code>, although if
- * <code>username</code> is null then this is equivalent to
<code>registerCredentials(name,null)</code>.
- * @param name the name of the repository and workspace
- * @param username the username to use, or null if the existing credentials for the
named workspace should be removed
- * @param password the password, may be null or empty
- * @return true if this overwrote existing credentials
- * @see #registerCredentials(String, Credentials)
- * @see #removeCredentials(String)
- */
- public boolean registerCredentials( String name, String username, char[] password )
{
- if (password == null && username != null) password = new char[] {};
- Credentials creds = username == null ? null : new SimpleCredentials(username,
password);
- return registerCredentials(name, creds);
- }
-
- /**
- * Register the credentials to be used for the named repository and workspace. Use
the same name as used to
- * {@link #createSession(String) create sessions}.
- * @param name the name of the repository and workspace
- * @param credentials the credentials to use, or null if the existing credentials for
the named workspace should be removed
- * @return true if this overwrote existing credentials
- * @see #registerCredentials(String, String, char[])
- * @see #removeCredentials(String)
- */
- public boolean registerCredentials( String name, Credentials credentials ) {
- boolean foundExisting = false;
- name = name != null ? name.trim() : null;
- if (credentials == null) {
- foundExisting = this.credentials.remove(name) != null;
- } else {
- foundExisting = this.credentials.put(name, credentials) != null;
- }
- return foundExisting;
- }
-
- /**
- * Remove any credentials associated with the named repository and workspace. This is
equivalent to calling
- * <code>registerCredentials(name,null)</code>.
- * @param name the name of the repository and workspace
- * @return true if existing credentials were found and removed, or false if no such
credentials existed
- * @see #registerCredentials(String, Credentials)
- */
- public boolean removeCredentials( String name ) {
- return registerCredentials(name, null);
- }
-
- /**
- * {@inheritDoc}
- */
- public Session createSession( String name ) throws RepositoryException {
- ArgCheck.isNotNull(name, "session name");
- name = name.trim();
- // Look up the Repository object in JNDI ...
- String repositoryName = getRepositoryName(name);
- Repository repository = null;
- try {
- repository = (Repository)this.context.lookup(repositoryName);
- } catch (NamingException err) {
- throw new
SystemFailureException(ServicesI18n.unableToFindRepositoryInJndi.text(repositoryName));
- }
-
- // Determine the name of the workspace ...
- String workspaceName = getWorkspaceName(name);
-
- // Look up any credentials, which may be null ...
- Credentials creds = this.credentials.get(name);
-
- // Create a session to the specified workspace and using the credentials (either
or both may be null) ...
- return repository.login(creds, workspaceName);
- }
-
- protected String getWorkspaceName( String name ) {
- assert name != null;
- int index = getIndexOfLastWorkspaceDelimiter(name);
- if (index == -1) return null;
- if ((index + 1) == name.length()) return null; // delim is the last character
- return name.substring(index + 1);
- }
-
- protected String getRepositoryName( String name ) {
- assert name != null;
- int index = getIndexOfLastWorkspaceDelimiter(name);
- if (index == -1) return name; // no delim
- if ((index + 1) == name.length()) return name.substring(0, index); // delim as
last character
- return name.substring(0, index);
- }
-
- protected int getIndexOfLastWorkspaceDelimiter( String name ) {
- int index = -1;
- for (char delim : this.workspaceDelims) {
- int i = name.lastIndexOf(delim);
- index = Math.max(index, i);
- }
- return index;
- }
-
-}
Deleted:
trunk/dna-repository/src/main/java/org/jboss/dna/services/RepositoryNodePath.java
===================================================================
---
trunk/dna-services/src/main/java/org/jboss/dna/services/RepositoryNodePath.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/RepositoryNodePath.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,104 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import net.jcip.annotations.Immutable;
-import org.jboss.dna.common.util.HashCode;
-
-/**
- * @author Randall Hauch
- */
-@Immutable
-public class RepositoryNodePath {
-
- protected static final Pattern PATTERN = Pattern.compile("([^:/]):(/.*)");
-
- public static RepositoryNodePath parse( String path, String
defaultRepositoryWorkspaceName ) {
- Matcher matcher = PATTERN.matcher(path);
- if (matcher.matches()) {
- try {
- return new RepositoryNodePath(matcher.group(1), matcher.group(2));
- } catch (Throwable t) {
- throw new
IllegalArgumentException(ServicesI18n.invalidRepositoryNodePath.text(path,
t.getMessage()));
- }
- }
- return new RepositoryNodePath(defaultRepositoryWorkspaceName, path);
-
- }
-
- private final String repositoryName;
- private final String nodePath;
- private final int hc;
-
- public RepositoryNodePath( String repositoryName, String nodePath ) {
- this.repositoryName = repositoryName;
- this.nodePath = nodePath;
- this.hc = HashCode.compute(this.repositoryName, this.nodePath);
- }
-
- /**
- * @return nodePath
- */
- public String getNodePath() {
- return this.nodePath;
- }
-
- /**
- * @return repositoryName
- */
- public String getRepositoryWorkspaceName() {
- return this.repositoryName;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int hashCode() {
- return this.hc;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public boolean equals( Object obj ) {
- if (obj == this) return true;
- if (obj instanceof RepositoryNodePath) {
- RepositoryNodePath that = (RepositoryNodePath)obj;
- if (!this.repositoryName.equals(that.repositoryName)) return false;
- if (!this.nodePath.equals(that.nodePath)) return false;
- return true;
- }
- return false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String toString() {
- return this.repositoryName + ":" + this.nodePath;
- }
-}
Deleted:
trunk/dna-repository/src/main/java/org/jboss/dna/services/ServiceAdministrator.java
===================================================================
---
trunk/dna-services/src/main/java/org/jboss/dna/services/ServiceAdministrator.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/ServiceAdministrator.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,144 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import java.util.concurrent.TimeUnit;
-import net.jcip.annotations.ThreadSafe;
-
-/**
- * Contract defining an administrative interface for controlling the running state of a
service.
- * @author Randall Hauch
- */
-@ThreadSafe
-public interface ServiceAdministrator {
-
- /**
- * The available states.
- * @author Randall Hauch
- */
- public static enum State {
- STARTED,
- PAUSED,
- SHUTDOWN;
- }
-
- /**
- * Return the current state of this system.
- * @return the current state
- */
- public State getState();
-
- /**
- * Set the state of the system. This method does nothing if the desired state matches
the current state.
- * @param state the desired state
- * @return this object for method chaining purposes
- * @see #setState(String)
- * @see #start()
- * @see #pause()
- * @see #shutdown()
- */
- public ServiceAdministrator setState( State state );
-
- /**
- * Set the state of the system. This method does nothing if the desired state matches
the current state.
- * @param state the desired state in string form
- * @return this object for method chaining purposes
- * @throws IllegalArgumentException if the specified state string is null or does not
match one of the predefined
- * {@link State predefined enumerated values}
- * @see #setState(State)
- * @see #start()
- * @see #pause()
- * @see #shutdown()
- */
- public ServiceAdministrator setState( String state );
-
- /**
- * Start monitoring and sequence the events. This method can be called multiple
times, including after the system is
- * {@link #pause() paused}. However, once the system is {@link #shutdown() shutdown},
it cannot be started or paused.
- * @return this object for method chaining purposes
- * @throws IllegalStateException if called when the system has been {@link
#shutdown() shutdown}.
- * @see #pause()
- * @see #shutdown()
- * @see #isStarted()
- */
- public ServiceAdministrator start();
-
- /**
- * Temporarily stop monitoring and sequencing events. This method can be called
multiple times, including after the system is
- * {@link #start() started}. However, once the system is {@link #shutdown()
shutdown}, it cannot be started or paused.
- * @return this object for method chaining purposes
- * @throws IllegalStateException if called when the system has been {@link
#shutdown() shutdown}.
- * @see #start()
- * @see #shutdown()
- * @see #isPaused()
- */
- public ServiceAdministrator pause();
-
- /**
- * Permanently stop monitoring and sequencing events. This method can be called
multiple times, but only the first call has an
- * effect. Once the system has been shutdown, it may not be {@link #start()
restarted} or {@link #pause() paused}.
- * @return this object for method chaining purposes
- * @see #start()
- * @see #pause()
- * @see #isShutdown()
- */
- public ServiceAdministrator shutdown();
-
- /**
- * Blocks until all work has completed execution after a shutdown request, or the
timeout occurs, or the current thread is
- * interrupted, whichever happens first.
- * @param timeout the maximum time to wait
- * @param unit the time unit of the timeout argument
- * @return <tt>true</tt> if this service terminated and
<tt>false</tt> if the timeout elapsed before termination
- * @throws InterruptedException if interrupted while waiting
- */
- boolean awaitTermination( long timeout, TimeUnit unit ) throws InterruptedException;
-
- /**
- * Return whether this system has been started and is currently running.
- * @return true if started and currently running, or false otherwise
- * @see #start()
- * @see #pause()
- * @see #isPaused()
- * @see #isShutdown()
- */
- public boolean isStarted();
-
- /**
- * Return whether this system is currently paused.
- * @return true if currently paused, or false otherwise
- * @see #pause()
- * @see #start()
- * @see #isStarted()
- * @see #isShutdown()
- */
- public boolean isPaused();
-
- /**
- * Return whether this system is stopped and unable to be restarted.
- * @return true if currently shutdown, or false otherwise
- * @see #shutdown()
- * @see #isPaused()
- * @see #isStarted()
- */
- public boolean isShutdown();
-}
Deleted: trunk/dna-repository/src/main/java/org/jboss/dna/services/ServicesI18n.java
===================================================================
--- trunk/dna-services/src/main/java/org/jboss/dna/services/ServicesI18n.java 2008-04-22
01:42:33 UTC (rev 95)
+++ trunk/dna-repository/src/main/java/org/jboss/dna/services/ServicesI18n.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,101 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import org.jboss.dna.common.i18n.I18n;
-
-/**
- * @author Randall Hauch
- */
-public final class ServicesI18n {
-
- static {
- try {
- I18n.initialize(ServicesI18n.class);
- } catch (final Exception err) {
- System.err.println(err);
- }
- }
-
- public static I18n invalidStateString;
- public static I18n serviceShutdowAndMayNotBeStarted;
- public static I18n serviceShutdowAndMayNotBePaused;
- public static I18n unableToFindRepositoryInJndi;
- public static I18n errorProcessingEvents;
- public static I18n errorFindingPropertyNameInPropertyAddedEvent;
- public static I18n errorFindingPropertyNameInPropertyChangedEvent;
- public static I18n errorFindingPropertyNameInPropertyRemovedEvent;
-
- public static I18n unableToObtainJsr94RuleAdministrator;
- public static I18n errorUsingJsr94RuleAdministrator;
- public static I18n unableToObtainJsr94ServiceProvider;
- public static I18n errorAddingOrUpdatingRuleSet;
- public static I18n errorRollingBackRuleSetAfterUpdateFailed;
- public static I18n errorReadingRulesAndProperties;
- public static I18n errorDeregisteringRuleSetBeforeUpdatingIt;
- public static I18n errorRecreatingRuleSet;
- public static I18n errorRemovingRuleSet;
- public static I18n errorRemovingRuleSetUponShutdown;
- public static I18n unableToFindRuleSet;
- public static I18n errorExecutingRuleSetWithGlobalsAndFacts;
- public static I18n unableToBuildRuleSetRegularExpressionPattern;
-
- public static I18n errorObtainingSessionToRepositoryWorkspace;
- public static I18n errorWritingProblemsOnRuleSet;
-
- public static I18n sequencingServiceName;
- public static I18n unableToChangeExecutionContextWhileRunning;
- public static I18n unableToStartSequencingServiceWithoutExecutionContext;
- public static I18n errorWhileSequencingNode;
- public static I18n errorInRepositoryWhileSequencingNode;
- public static I18n errorFindingSequencersToRunAgainstNode;
- public static I18n errorInRepositoryWhileFindingSequencersToRunAgainstNode;
- public static I18n executionContextHasBeenClosed;
- public static I18n sequencerTask;
- public static I18n sequencerSubtask;
- public static I18n unableToFindPropertyForSequencing;
- public static I18n sequencingPropertyOnNode;
- public static I18n writingOutputSequencedFromPropertyOnNodes;
-
- public static I18n errorReadingPropertiesFromContainerNode;
- public static I18n requiredPropertyOnNodeWasExpectedToBeStringValue;
- public static I18n optionalPropertyOnNodeWasExpectedToBeStringValue;
- public static I18n requiredPropertyOnNodeWasExpectedToBeStringArrayValue;
- public static I18n optionalPropertyOnNodeWasExpectedToBeStringArrayValue;
- public static I18n requiredPropertyOnNodeCouldNotBeRead;
- public static I18n optionalPropertyOnNodeCouldNotBeRead;
- public static I18n requiredPropertyIsMissingFromNode;
- public static I18n errorGettingRequiredPropertyFromNode;
- public static I18n errorGettingOptionalPropertyFromNode;
- public static I18n errorClosingBinaryStreamForPropertyFromNode;
- public static I18n requiredNodeDoesNotExistRelativeToNode;
- public static I18n errorGettingNodeRelativeToNode;
- public static I18n unknownPropertyValueType;
-
- public static I18n pathExpressionIsInvalid;
- public static I18n pathExpressionMayNotBeBlank;
- public static I18n pathExpressionHasInvalidSelect;
- public static I18n pathExpressionHasInvalidMatch;
-
- public static I18n invalidRepositoryNodePath;
-
-}
Deleted: trunk/dna-repository/src/main/java/org/jboss/dna/services/SessionFactory.java
===================================================================
--- trunk/dna-services/src/main/java/org/jboss/dna/services/SessionFactory.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/main/java/org/jboss/dna/services/SessionFactory.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,35 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-
-package org.jboss.dna.services;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-
-/**
- * @author Randall Hauch
- */
-public interface SessionFactory {
-
- Session createSession( String name ) throws RepositoryException;
-
-}
Added:
trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties
===================================================================
---
trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties
(rev 0)
+++
trunk/dna-repository/src/main/resources/org/jboss/dna/repository/RepositoryI18n.properties 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,61 @@
+invalidStateString = Invalid state parameter "{0}"
+serviceShutdowAndMayNotBeStarted = The {0} has been shutdown and may not be (re)started
+serviceShutdowAndMayNotBePaused = The {0} has been shutdown and my not be paused
+unableToFindRepositoryInJndi = Unable to find a JCR repository in JNDI at '{0}'
+errorProcessingEvents = Error processing events from {0}
+errorFindingPropertyNameInPropertyAddedEvent = Error finding the name of the added
property in the event path {0}
+errorFindingPropertyNameInPropertyChangedEvent = Error finding the name of the changed
property in the event path {0}
+errorFindingPropertyNameInPropertyRemovedEvent = Error finding the name of the removed
property in the event path {0}
+
+unableToObtainJsr94RuleAdministrator = Unable to obtain the rule administrator for JSR-94
service provider {0} ({1}) while adding/updating rule set {2}
+errorUsingJsr94RuleAdministrator = Error using rule administrator for JSR-94 service
provider {0} ({1}) while adding/updating rule set {2}
+unableToObtainJsr94ServiceProvider = Error using rule administrator for JSR-94 service
provider {0} ({1})
+errorAddingOrUpdatingRuleSet = Error adding/updating rule set '{0}'
+errorRollingBackRuleSetAfterUpdateFailed = Error rolling back rule set '{0}'
after new rule set failed
+errorReadingRulesAndProperties = Error reading the rules and properties while
adding/updating rule set '{0}'
+errorDeregisteringRuleSetBeforeUpdatingIt = Error deregistering rule set '{0}'
before updating it
+errorRecreatingRuleSet = Error (re)creating the rule set '{0}'
+errorRemovingRuleSet = Error removing rule set '{0}'
+errorRemovingRuleSetUponShutdown = Error removing rule set '{0}' upon shutdown
+unableToFindRuleSet = Unable to find rule set with name '{0}'
+errorExecutingRuleSetWithGlobalsAndFacts = Error executing rule set '{0}' and
with globals {1} and facts {2}
+unableToBuildRuleSetRegularExpressionPattern = Unable to build the rule set name pattern
"{0}" using the supplied absolute path ("{1}"): {2}
+
+errorObtainingSessionToRepositoryWorkspace = Error obtaining JCR session to repository
workspace {0}
+errorWritingProblemsOnRuleSet = Error while writing problems on rule set node {0}
+
+sequencingServiceName = Sequencing Service
+unableToChangeExecutionContextWhileRunning = Unable to change the execution context while
running
+unableToStartSequencingServiceWithoutExecutionContext = Unable to start the Sequencing
Service without an execution context
+errorWhileSequencingNode = Error while sequencer {0} is sequencing node {1}
+errorInRepositoryWhileSequencingNode = Error in repository while sequencer {0} is
sequencing node {1}
+errorInRepositoryWhileFindingSequencersToRunAgainstNode = Error in repository while
finding sequencers to run against node {0}
+errorFindingSequencersToRunAgainstNode = Error finding sequencers to run against node
{0}
+executionContextHasBeenClosed = This execution context has been closed and may not be
used to create another session
+sequencerTask = Sequencing {0}
+sequencerSubtask = running {0}
+unableToFindPropertyForSequencing = Unable to find the {0} property while sequencing node
{1}
+sequencingPropertyOnNode = Sequencing the {0} property on node {1}
+writingOutputSequencedFromPropertyOnNodes = Writing the output of the sequencing of the
{0} property on node {1} to {2} nodes
+
+errorReadingPropertiesFromContainerNode = Error reading properties from property
container node {0}
+requiredPropertyOnNodeWasExpectedToBeStringValue = The required {0} property on node {1}
was expected to be a string value
+optionalPropertyOnNodeWasExpectedToBeStringValue = The optional {0} property on node {1}
was expected to be a string value
+requiredPropertyOnNodeWasExpectedToBeStringArrayValue = The required {0} property on node
{1} was expected to be a string array
+optionalPropertyOnNodeWasExpectedToBeStringArrayValue = The optional {0} property on node
{1} was expected to be a string array
+requiredPropertyOnNodeCouldNotBeRead = The required {0} property on node {1} could not be
read
+optionalPropertyOnNodeCouldNotBeRead = The optional {0} property on node {1} could not be
read
+requiredPropertyIsMissingFromNode = The required {0} property is missing from node {1}
+errorGettingRequiredPropertyFromNode = Error while getting the required property {0} from
node {1}
+errorGettingOptionalPropertyFromNode = Error while getting the optional property {0} from
node {1}
+errorClosingBinaryStreamForPropertyFromNode = Error while closing the binary stream for
property {0} on node {1}
+requiredNodeDoesNotExistRelativeToNode = The required node {0} does not exist relative to
{1}
+errorGettingNodeRelativeToNode = Error while getting the node {0} (relative to {1})
+unknownPropertyValueType = Property value {0} is of a type ({1}) that is unknown
+
+pathExpressionMayNotBeBlank = The path expression may not be blank
+pathExpressionIsInvalid = The path expression {0} is not valid
+pathExpressionHasInvalidSelect = Invalid select expression '{0}' in the path
expression '{1}=>{2}'
+pathExpressionHasInvalidMatch = Invalid match expression '{0}' in the path
expression '{1}=>{2}'
+
+invalidRepositoryNodePath = The repository node path '{0}' is not valid: {1}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/observation/NodeChangeTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/observation/NodeChangeTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/observation/NodeChangeTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,65 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.observation;
+
+import java.util.HashSet;
+import java.util.Set;
+import javax.jcr.observation.Event;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class NodeChangeTest {
+
+ private String validRepositoryWorkspaceName;
+ private String validAbsolutePath;
+ private int validEventTypes;
+ private Set<String> validModifiedProperties;
+ private Set<String> validRemovedProperties;
+ private NodeChange nodeChange;
+
+ @Before
+ public void beforeEach() throws Exception {
+ validRepositoryWorkspaceName = "repositoryX";
+ validAbsolutePath = "/a/b/c/d";
+ validEventTypes = Event.NODE_ADDED | Event.PROPERTY_ADDED |
Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+ validModifiedProperties = new HashSet<String>();
+ validRemovedProperties = new HashSet<String>();
+ validModifiedProperties.add("jcr:name");
+ validModifiedProperties.add("jcr:title");
+ validRemovedProperties.add("jcr:mime");
+ nodeChange = new NodeChange(validRepositoryWorkspaceName, validAbsolutePath,
validEventTypes, validModifiedProperties, validRemovedProperties);
+ }
+
+ @Test( expected = UnsupportedOperationException.class )
+ public void shouldHaveUnmodifiableSetOfModifiedPropertyNames() {
+ nodeChange.getModifiedProperties().clear();
+ }
+
+ @Test( expected = UnsupportedOperationException.class )
+ public void shouldHaveUnmodifiableSetOfRemovedPropertyNames() {
+ nodeChange.getRemovedProperties().clear();
+ }
+}
Added: trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleInput.java
===================================================================
--- trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleInput.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleInput.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,79 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+public final class RuleInput {
+
+ String mimeType = "";
+ String header = "";
+ String fileName = "";
+
+ /**
+ * @return fileName
+ */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /**
+ * @return header
+ */
+ public String getHeader() {
+ return header;
+ }
+
+ /**
+ * @return mimeType
+ */
+ public String getMimeType() {
+ return mimeType;
+ }
+
+ /**
+ * @param fileName Sets fileName to the specified value.
+ */
+ public void setFileName( String fileName ) {
+ this.fileName = fileName;
+ }
+
+ /**
+ * @param header Sets header to the specified value.
+ */
+ public void setHeader( String header ) {
+ this.header = header;
+ }
+
+ /**
+ * @param mimeType Sets mimeType to the specified value.
+ */
+ public void setMimeType( String mimeType ) {
+ this.mimeType = mimeType;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return this.fileName + " (" + this.mimeType + ") => " +
this.header;
+ }
+}
Added: trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleResult.java
===================================================================
--- trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleResult.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleResult.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,41 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+/**
+ * @author John Verhaeg
+ */
+public final class RuleResult {
+
+ String name;
+
+ public RuleResult( String name ) {
+ this.name = name != null ? name : "";
+ }
+
+ /**
+ * @return name
+ */
+ public String getName() {
+ return name;
+ }
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleServiceTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleServiceTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleServiceTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,248 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import java.io.IOException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.jboss.dna.common.component.ClassLoaderFactory;
+import org.jboss.dna.common.component.StandardClassLoaderFactory;
+import org.jboss.dna.common.util.IoUtil;
+import org.jboss.dna.common.util.Logger;
+import org.jboss.dna.repository.rules.InvalidRuleSetException;
+import org.jboss.dna.repository.rules.RuleService;
+import org.jboss.dna.repository.rules.RuleSet;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class RuleServiceTest {
+
+ protected static final String DROOLS_PROVIDER_CLASSNAME =
"org.drools.jsr94.rules.RuleServiceProviderImpl";
+ protected static final String DROOLS_PROVIDER_URI = "http://drools.org/";
+
+ private RuleService ruleService;
+ private ClassLoaderFactory classLoaderFactory;
+
+ protected RuleSet createDroolsRuleSet( String desc ) throws IOException {
+ String name = "SampleRuleSet";
+ String description = desc != null ? desc : "This is a sample rule set that
uses Drools";
+ String providerClassname = DROOLS_PROVIDER_CLASSNAME;
+ String providerUri = DROOLS_PROVIDER_URI;
+ String[] providerClasspath = new String[] {};
+ String ruleSetUri = null;
+ URL ruleFile = this.getClass().getResource("/rule_test.dslr");
+ URL dslFile = this.getClass().getResource("/rule_test.dsl");
+ String rules = IoUtil.read(ruleFile.openStream());
+ byte[] dslBytes = IoUtil.readBytes(dslFile.openStream());
+ Map<String, Object> properties = new HashMap<String, Object>();
+ properties.put("javax.rules.admin.RuleExecutionSet.source",
"drl"); // as opposed to "xml"
+ properties.put("javax.rules.admin.RuleExecutionSet.dsl", dslBytes);
+ return new RuleSet(name, description, providerClassname, providerClasspath,
providerUri, ruleSetUri, rules, properties);
+ }
+
+ protected RuleSet createRuleSetWithInvalidProvider() throws IOException {
+ String name = "SampleRuleSet"; // same name as Drools rule set
+ String description = "This is a sample rule set that uses Drools";
+ String providerClassname = DROOLS_PROVIDER_CLASSNAME;
+ String providerUri = DROOLS_PROVIDER_URI + "this/is/incorrect";
+ String[] providerClasspath = new String[] {};
+ String ruleSetUri = null;
+ URL ruleFile = this.getClass().getResource("/rule_test.dslr");
+ URL dslFile = this.getClass().getResource("/rule_test.dsl");
+ String rules = IoUtil.read(ruleFile.openStream());
+ byte[] dslBytes = IoUtil.readBytes(dslFile.openStream());
+ Map<String, Object> properties = new HashMap<String, Object>();
+ properties.put("javax.rules.admin.RuleExecutionSet.source",
"drl"); // as opposed to "xml"
+ properties.put("javax.rules.admin.RuleExecutionSet.dsl", dslBytes);
+ return new RuleSet(name, description, providerClassname, providerClasspath,
providerUri, ruleSetUri, rules, properties);
+ }
+
+ @Before
+ public void beforeEach() throws Exception {
+ this.ruleService = new RuleService();
+ this.classLoaderFactory = new StandardClassLoaderFactory();
+ }
+
+ @After
+ public void afterEach() throws Exception {
+ this.ruleService.getAdministrator().shutdown();
+ }
+
+ @Test
+ public void shouldHaveDefaultClasspathFactoryUponConstruction() {
+ assertThat(ruleService.getClassLoaderFactory(), is(notNullValue()));
+ assertThat(ruleService.getClassLoaderFactory(),
is(sameInstance(RuleService.DEFAULT_CLASSLOADER_FACTORY)));
+ }
+
+ @Test
+ public void shouldHaveLoggerUponConstruction() {
+ assertThat(ruleService.getLogger(), is(notNullValue()));
+ }
+
+ @Test
+ public void shouldHaveEmptyRuleSetCollectionUponConstruction() {
+ assertThat(ruleService.getRuleSets(), is(notNullValue()));
+ assertThat(ruleService.getRuleSets().isEmpty(), is(true));
+ }
+
+ @Test( expected = UnsupportedOperationException.class )
+ public void shouldReturnUnmodifiableCollectionOfRuleSets() {
+ ruleService.getRuleSets().add(null);
+ }
+
+ @Test
+ public void shouldSetClassLoaderFactoryToDefaultWhenPassedNull() {
+ assertThat(ruleService.getClassLoaderFactory(),
is(sameInstance(RuleService.DEFAULT_CLASSLOADER_FACTORY)));
+ ruleService.setClassLoaderFactory(classLoaderFactory);
+ assertThat(ruleService.getClassLoaderFactory(),
is(sameInstance(classLoaderFactory)));
+ ruleService.setClassLoaderFactory(null);
+ assertThat(ruleService.getClassLoaderFactory(),
is(sameInstance(RuleService.DEFAULT_CLASSLOADER_FACTORY)));
+ }
+
+ @Test
+ public void shouldSetLoggerToDefaultWhenPassedNull() {
+ Logger defaultLogger = ruleService.getLogger();
+ assertThat(ruleService.getLogger(), is(notNullValue()));
+ Logger orgLogger = Logger.getLogger("org");
+ ruleService.setLogger(orgLogger);
+ assertThat(ruleService.getLogger().getName(), is(orgLogger.getName()));
+ assertThat(ruleService.getLogger(), is(sameInstance(orgLogger)));
+ ruleService.setLogger(null);
+ assertThat(ruleService.getLogger().getName(), is(defaultLogger.getName()));
+ assertThat(ruleService.getLogger(), is(not(orgLogger)));
+ }
+
+ @Test( expected = InvalidRuleSetException.class )
+ public void shouldNotAddInvalidRuleSet() throws Exception {
+ ruleService.addRuleSet(createRuleSetWithInvalidProvider());
+
+ }
+
+ @Test
+ public void shouldAddValidRuleSet() throws Exception {
+ int numRuleSetsBefore = ruleService.getRuleSets().size();
+ RuleSet validRuleSet = createDroolsRuleSet(null);
+ assertThat(ruleService.addRuleSet(validRuleSet), is(true));
+ assertThat(ruleService.getRuleSets().size(), is(numRuleSetsBefore + 1));
+ }
+
+ @Test
+ public void shouldNotUpdateValidRuleSetWithInvalidRuleSet() throws Exception {
+ RuleSet validRuleSet = createDroolsRuleSet(null);
+ final String ruleSetName = validRuleSet.getName();
+
+ assertThat(ruleService.addRuleSet(validRuleSet), is(true));
+ assertThat(ruleService.getRuleSets().size(), is(1));
+ assertThat(ruleService.getRuleSets().iterator().next(), is(validRuleSet));
+ // Verify this rule set works ...
+ executeRuleSet(ruleSetName);
+
+ // Try to update the rule set with something that's invalid ...
+ RuleSet invalidRuleSet = createRuleSetWithInvalidProvider();
+ assertThat(invalidRuleSet.getName(), is(ruleSetName));
+ assertThat(invalidRuleSet.getName(), is(validRuleSet.getName()));
+ try {
+ ruleService.addRuleSet(invalidRuleSet);
+ fail("Should not get here");
+ } catch (InvalidRuleSetException e) {
+ // This is expected since it was a bad rule set ...
+ }
+
+ // Verify the original rule set is still there and still works ...
+ assertThat(ruleService.getRuleSets().size(), is(1));
+ assertThat(ruleService.getRuleSets().iterator().next(), is(validRuleSet));
+ executeRuleSet(ruleSetName);
+ }
+
+ @Test
+ public void shouldModifyExistingRuleSetWhenAddedOnlyWhenChanged() throws Exception {
+ int numRuleSetsBefore = ruleService.getRuleSets().size();
+ RuleSet validRuleSet = createDroolsRuleSet(null);
+ assertThat(ruleService.addRuleSet(validRuleSet), is(true));
+ assertThat(ruleService.getRuleSets().size(), is(numRuleSetsBefore + 1));
+
+ // Try to add an unchanged rule set ...
+ RuleSet validRuleSet2 = createDroolsRuleSet(null);
+ assertThat(validRuleSet2.hasChanged(validRuleSet), is(false));
+ assertThat(ruleService.addRuleSet(validRuleSet2), is(false));
+ assertThat(ruleService.getRuleSets().size(), is(numRuleSetsBefore + 1));
+
+ // Try to add a changed rule set ...
+ RuleSet validRuleSet3 = createDroolsRuleSet("This is an alternative
description that causes it to be changed");
+ assertThat(validRuleSet3.hasChanged(validRuleSet), is(true));
+ assertThat(ruleService.addRuleSet(validRuleSet3), is(true));
+ assertThat(ruleService.getRuleSets().size(), is(numRuleSetsBefore + 1));
+ }
+
+ @Test
+ public void shouldExecuteRuleSet() throws Exception {
+ RuleSet validRuleSet = createDroolsRuleSet(null);
+ assertThat(ruleService.addRuleSet(validRuleSet), is(true));
+
+ executeRuleSet(validRuleSet.getName());
+ }
+
+ /**
+ * @param validRuleSet
+ */
+ protected void executeRuleSet( String ruleSetName ) {
+ // Create some simple fact objects ...
+ RuleInput info = new RuleInput();
+ info.setFileName("someOtherInput.dsl");
+ info.setHeader("This is the sequencer header");
+ info.setMimeType("text");
+
+ // Run the rules ...
+ Set<String> output = new LinkedHashSet<String>();
+ Map<String, Object> globals = new HashMap<String, Object>();
+ globals.put("output", output);
+
+ List<?> results = ruleService.executeRules(ruleSetName, globals, info);
+ assertThat(results, is(notNullValue()));
+ assertThat(results.isEmpty(), is(false));
+ assertThat(results.size(), is(2));
+ assertTrue(results.get(0) instanceof RuleResult || results.get(1) instanceof
RuleResult);
+ // Object result = results.get(0);
+ // assertThat(result, is(instanceOf(RuleResult.class)));
+ assertThat(output.isEmpty(), is(false));
+ Object sequencer = output.iterator().next();
+ assertThat(sequencer, is(instanceOf(String.class)));
+ assertThat(((String)sequencer), is("A"));
+ }
+
+}
Added: trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleSetTest.java
===================================================================
--- trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleSetTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/rules/RuleSetTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,299 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.rules;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.junit.Assert.assertThat;
+import java.io.Reader;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+import org.jboss.dna.common.util.IoUtil;
+import org.jboss.dna.repository.rules.RuleSet;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class RuleSetTest {
+
+ private RuleSet ruleSet;
+ private String validName;
+ private String validDescription;
+ private String validClassname;
+ private String[] validClasspath;
+ private String validProviderUri;
+ private String validRuleSetUri;
+ private String validRules;
+ private Map<String, Object> validProperties;
+
+ @Before
+ public void beforeEach() throws Exception {
+ this.validName = "This is a valid name";
+ this.validDescription = "This is a valid description";
+ this.validClassname = "com.acme.SuperDuper";
+ this.validClasspath = new String[] {"classpath1",
"classpath2"};
+ this.validProviderUri =
"http://www.acme.com/super/duper/rules_engine";
+ this.validRuleSetUri = "com.something.whatever doesn't really need to be
a URI";
+ this.validRules = "when something is true then cheer";
+ this.validProperties = new HashMap<String, Object>();
+ this.validProperties.put("key1", "value1");
+ this.validProperties.put("key2", null);
+ this.validProperties.put("key3", "value3".getBytes());
+ this.ruleSet = new RuleSet(validName, validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullName() {
+ new RuleSet(null, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyName() {
+ new RuleSet("", validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankName() {
+ new RuleSet(" \t ", validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test
+ public void shouldTrimName() {
+ validName = " this is a valid name with leading and trailing whitespace
";
+ ruleSet = new RuleSet(validName, validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getName(), is(validName.trim()));
+ }
+
+ @Test
+ public void shouldAllowNullOrEmptyOrBlankDescriptionAndShouldReplaceWithEmptyString()
{
+ ruleSet = new RuleSet(validName, null, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getDescription(), is(""));
+ ruleSet = new RuleSet(validName, "", validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getDescription(), is(""));
+ ruleSet = new RuleSet(validName, " \t ", validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getDescription(), is(""));
+ }
+
+ @Test
+ public void shouldTrimDescription() {
+ ruleSet = new RuleSet(validName, " valid ", validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getDescription(), is("valid"));
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullClassname() {
+ new RuleSet(validName, validDescription, null, validClasspath, validProviderUri,
validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyClassname() {
+ new RuleSet(validName, validDescription, "", validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankClassname() {
+ new RuleSet(validName, validDescription, " ", validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowClassnameThatDoesNotFollowJavaNamingRules() {
+ new RuleSet(validName, validDescription, "not a valid classname",
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test
+ public void shouldAllowNullOrEmptyClasspath() {
+ new RuleSet(validName, validDescription, validClassname, null, validProviderUri,
validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test
+ public void shouldRemoveNullOrBlankClasspathItems() {
+ new RuleSet(validName, validDescription, validClassname, new String[] {},
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test
+ public void shouldRemoveDuplicateClasspathItemsInCaseSensitiveManner() {
+ validClasspath = new String[] {"path1", "path2",
"path1", "path3", "path2"};
+ ruleSet = new RuleSet(validName, validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getComponentClasspathArray(), is(new String[]
{"path1", "path2", "path3"}));
+
+ validClasspath = new String[] {"path1", "path2",
"path1", "path3", "Path2"};
+ ruleSet = new RuleSet(validName, validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getComponentClasspathArray(), is(new String[]
{"path1", "path2", "path3", "Path2"}));
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullProviderUri() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath, null,
validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyProviderUri() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
"", validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankProviderUri() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath, "
\t ", validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test
+ public void shouldUseNameInPlaceOfNullRuleSetUri() {
+ ruleSet = new RuleSet(validName, validDescription, validClassname,
validClasspath, validProviderUri, null, validRules, validProperties);
+ assertThat(ruleSet.getRuleSetUri(), is(ruleSet.getName()));
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyRuleSetUri() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, "", validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankRuleSetUri() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, " \t ", validRules, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullRules() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, null, validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyRules() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, "", validProperties);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankRules() {
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, " \t ", validProperties);
+ }
+
+ @Test
+ public void shouldAllowNullOrEmptyProperties() {
+ validProperties = null;
+ ruleSet = new RuleSet(validName, validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(ruleSet.getProperties(), is(notNullValue()));
+ }
+
+ @Test
+ public void
shouldWrapByteArrayPropertyValuesWithInputStreamInExecutionSetProperties() throws
Exception {
+ Map<Object, Object> executionSetProps =
ruleSet.getExecutionSetProperties();
+ assertThat(executionSetProps.size(), is(ruleSet.getProperties().size()));
+ assertThat(executionSetProps.keySet(), is((Set<Object>)new
HashSet<Object>(ruleSet.getProperties().keySet())));
+ Iterator<Map.Entry<Object, Object>> executionSetPropIter =
executionSetProps.entrySet().iterator();
+ Iterator<Map.Entry<String, Object>> propIter =
ruleSet.getProperties().entrySet().iterator();
+ while (executionSetPropIter.hasNext() && propIter.hasNext()) {
+ Map.Entry<Object, Object> execSetEntry = executionSetPropIter.next();
+ Object execSetKey = execSetEntry.getKey();
+ Object execSetValue = execSetEntry.getValue();
+ Map.Entry<String, Object> propertyEntry = propIter.next();
+ String propertyKey = propertyEntry.getKey();
+ Object propertyValue = propertyEntry.getValue();
+ // Check the entries ...
+ assertThat(execSetKey, is((Object)propertyKey));
+ if (propertyValue instanceof byte[]) {
+ assertThat(execSetValue, is(instanceOf(Reader.class)));
+ String streamValueAsString = IoUtil.read((Reader)execSetValue);
+ String propertyValueAsString = new String((byte[])propertyValue);
+ assertThat(streamValueAsString, is(propertyValueAsString));
+ } else {
+ assertThat(execSetValue, is(propertyValue));
+ }
+ }
+ assertThat(executionSetPropIter.hasNext(), is(false));
+ assertThat(propIter.hasNext(), is(false));
+
+ new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ }
+
+ @Test
+ public void shouldConsiderAnyAttributeDifferencesAsChange() {
+ RuleSet copy = ruleSet.clone();
+ assertThat(copy.hasChanged(ruleSet), is(false));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ copy = new RuleSet(validName + "x", validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ copy = new RuleSet(validName, validDescription + "x", validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ copy = new RuleSet(validName, validDescription, validClassname + "x",
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri + "x", validRuleSetUri, validRules, validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ validClasspath = new String[] {"classpath1", "classpath2x"};
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri + "x", validRules, validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules + "x", validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+
+ validProperties.remove("key1");
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.hasChanged(ruleSet), is(true));
+ assertThat(copy.hasChanged(copy), is(false));
+ }
+
+ @Test
+ public void shouldConsiderOnlyNameWhenDeterminingIfSame() {
+ RuleSet copy = ruleSet.clone();
+ assertThat(copy.equals(ruleSet), is(true));
+
+ copy = new RuleSet(validName + "x", validDescription, validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.equals(ruleSet), is(false));
+
+ copy = new RuleSet(validName, validDescription + "x", validClassname,
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.equals(ruleSet), is(true));
+
+ copy = new RuleSet(validName, validDescription, validClassname + "x",
validClasspath, validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.equals(ruleSet), is(true));
+
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri + "x", validRuleSetUri, validRules, validProperties);
+ assertThat(copy.equals(ruleSet), is(true));
+
+ validClasspath = new String[] {"classpath1", "classpath2x"};
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri + "x", validRules, validProperties);
+ assertThat(copy.equals(ruleSet), is(true));
+
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules + "x", validProperties);
+ assertThat(copy.equals(ruleSet), is(true));
+
+ validProperties.remove("key1");
+ copy = new RuleSet(validName, validDescription, validClassname, validClasspath,
validProviderUri, validRuleSetUri, validRules, validProperties);
+ assertThat(copy.equals(ruleSet), is(true));
+ }
+
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerA.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerA.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerA.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,102 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.repository.sequencers;
+
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.jcr.Node;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.CoreI18n;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.sequencers.Sequencer;
+import org.jboss.dna.repository.sequencers.SequencerConfig;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.RepositoryNodePath;
+
+/**
+ * A sequencer that can be used for basic unit testing.
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public class MockSequencerA implements Sequencer {
+
+ private SequencerConfig config;
+ private AtomicInteger counter = new AtomicInteger();
+ private CountDownLatch latch = new CountDownLatch(0);
+
+ public void setExpectedCount( int numExpected ) {
+ this.latch = new CountDownLatch(numExpected);
+ }
+
+ public boolean awaitExecution( long timeout, TimeUnit unit ) throws
InterruptedException {
+ return this.latch.await(timeout, unit);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setConfiguration( SequencerConfig sequencerConfiguration ) {
+ this.config = sequencerConfiguration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void execute( Node input, String sequencedPropertyName, NodeChange changes,
Set<RepositoryNodePath> outputPaths, ExecutionContext context, ProgressMonitor
progress ) {
+ try {
+ progress.beginTask(1, CoreI18n.passthrough, "Incrementing
counter");
+ // increment the counter and record the progress ...
+ this.counter.incrementAndGet();
+ this.latch.countDown();
+ progress.worked(1);
+ } finally {
+ progress.done();
+ }
+ }
+
+ public int getCounter() {
+ return this.counter.get();
+ }
+
+ public boolean isConfigured() {
+ return this.config != null;
+ }
+
+ /**
+ * @return config
+ */
+ public SequencerConfig getConfiguration() {
+ return this.config;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return (this.config != null ? this.config.getName() :
"SampleSequencer") + " [" + this.getCounter() + "]";
+ }
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerB.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerB.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/MockSequencerB.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,102 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.repository.sequencers;
+
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import javax.jcr.Node;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.common.CoreI18n;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.sequencers.Sequencer;
+import org.jboss.dna.repository.sequencers.SequencerConfig;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.RepositoryNodePath;
+
+/**
+ * A sequencer that can be used for basic unit testing.
+ * @author Randall Hauch
+ */
+@ThreadSafe
+public class MockSequencerB implements Sequencer {
+
+ private SequencerConfig config;
+ private AtomicInteger counter = new AtomicInteger();
+ private CountDownLatch latch = new CountDownLatch(0);
+
+ public void setExpectedCount( int numExpected ) {
+ this.latch = new CountDownLatch(numExpected);
+ }
+
+ public boolean awaitExecution( long timeout, TimeUnit unit ) throws
InterruptedException {
+ return this.latch.await(timeout, unit);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setConfiguration( SequencerConfig sequencerConfiguration ) {
+ this.config = sequencerConfiguration;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void execute( Node input, String sequencedPropertyName, NodeChange changes,
Set<RepositoryNodePath> outputPaths, ExecutionContext context, ProgressMonitor
progress ) {
+ try {
+ progress.beginTask(1, CoreI18n.passthrough, "Incrementing
counter");
+ // increment the counter and record the progress ...
+ this.counter.incrementAndGet();
+ this.latch.countDown();
+ progress.worked(1);
+ } finally {
+ progress.done();
+ }
+ }
+
+ public int getCounter() {
+ return this.counter.get();
+ }
+
+ public boolean isConfigured() {
+ return this.config != null;
+ }
+
+ /**
+ * @return config
+ */
+ public SequencerConfig getConfiguration() {
+ return this.config;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String toString() {
+ return (this.config != null ? this.config.getName() :
"SampleSequencer") + " [" + this.getCounter() + "]";
+ }
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerConfigTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerConfigTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerConfigTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,131 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.repository.sequencers;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import org.jboss.dna.repository.sequencers.SequencerConfig;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class SequencerConfigTest {
+
+ private SequencerConfig configA;
+ private SequencerConfig configB;
+ private SequencerConfig configA2;
+ private String validName;
+ private String validDescription;
+ private String validClassname;
+ private String[] validPathExpressions;
+ private String[] validMavenIds;
+
+ @Before
+ public void beforeEach() throws Exception {
+ this.validName = "valid configuration name";
+ this.validDescription = "a sequencer";
+ this.validClassname = MockSequencerA.class.getName();
+ this.validPathExpressions = new String[] {"/a/b/c/d[e/@attribute] =>
."};
+ this.validMavenIds = new String[]
{"com.acme:configA:1.0,com.acme:configB:1.0"};
+ this.configA = new SequencerConfig("configA", validDescription,
MockSequencerA.class.getName(), validMavenIds, validPathExpressions);
+ this.configB = new SequencerConfig("configB", validDescription,
MockSequencerB.class.getName(), validMavenIds, validPathExpressions);
+ this.configA2 = new SequencerConfig("conFigA", validDescription,
MockSequencerA.class.getName(), validMavenIds, validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullNameInConstructor() {
+ new SequencerConfig(null, validDescription, validClassname, validMavenIds,
validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyNameInConstructor() {
+ new SequencerConfig("", validDescription, validClassname,
validMavenIds, validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankNameInConstructor() {
+ new SequencerConfig(" \t", validDescription, validClassname,
validMavenIds, validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowNullClassNameInConstructor() {
+ new SequencerConfig(validName, validDescription, null, validMavenIds,
validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowEmptyClassNameInConstructor() {
+ new SequencerConfig(validName, validDescription, "", validMavenIds,
validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowBlankClassNameInConstructor() {
+ new SequencerConfig(validName, validDescription, " \t",
validMavenIds, validPathExpressions);
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotAllowInvalidClassNameInConstructor() {
+ new SequencerConfig(validName, validDescription, "12.this is not a valid
classname", validMavenIds, validPathExpressions);
+ }
+
+ @Test
+ public void shouldConsiderSameIfNamesAreEqualIgnoringCase() {
+ assertThat(configA.equals(configA2), is(true));
+ }
+
+ @Test
+ public void shouldConsiderNotSameIfNamesAreNotEqualIgnoringCase() {
+ assertThat(configA.equals(configB), is(false));
+ }
+
+ @Test
+ public void shouldNotAddNullOrBlankPathExpressions() {
+ assertThat(SequencerConfig.buildPathExpressionSet(null, "", "
", validPathExpressions[0]).size(), is(1));
+ }
+
+ @Test
+ public void shouldNotAddSamePathExpressionMoreThanOnce() {
+ assertThat(SequencerConfig.buildPathExpressionSet(validPathExpressions[0],
validPathExpressions[0], validPathExpressions[0]).size(), is(1));
+ }
+
+ @Test
+ public void shouldHaveNonNullPathExpressionCollectionWhenThereAreNoPathExpressions()
{
+ configA = new SequencerConfig("configA", validDescription,
validClassname, validMavenIds);
+ assertThat(configA.getPathExpressions().size(), is(0));
+ }
+
+ @Test
+ public void shouldSetClasspathWithValidMavenIds() {
+ assertThat(configA.getComponentClasspath().size(), is(validMavenIds.length));
+ assertThat(configA.getComponentClasspathArray(), is(validMavenIds));
+ }
+
+ @Test
+ public void shouldGetNonNullSequencerClasspathWhenEmpty() {
+ configA = new SequencerConfig("configA", validDescription,
validClassname, null, validPathExpressions);
+ assertThat(configA.getComponentClasspath().size(), is(0));
+ assertThat(configA.getComponentClasspathArray().length, is(0));
+ }
+
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerPathExpressionTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerPathExpressionTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencerPathExpressionTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,356 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.junit.Assert.assertThat;
+import org.jboss.dna.repository.sequencers.InvalidSequencerPathExpression;
+import org.jboss.dna.repository.sequencers.SequencerPathExpression;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class SequencerPathExpressionTest {
+
+ private SequencerPathExpression expr;
+
+ @Before
+ public void beforeEach() throws Exception {
+ expr = new SequencerPathExpression(".*", "/output");
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldNotCompileNullExpression() {
+ SequencerPathExpression.compile(null);
+ }
+
+ @Test( expected = InvalidSequencerPathExpression.class )
+ public void shouldNotCompileZeroLengthExpression() {
+ SequencerPathExpression.compile("");
+ }
+
+ @Test( expected = InvalidSequencerPathExpression.class )
+ public void shouldNotCompileBlankExpression() {
+ SequencerPathExpression.compile(" ");
+ }
+
+ @Test
+ public void shouldCompileExpressionWithOnlySelectionExpression() {
+ expr = SequencerPathExpression.compile("/a/b/c");
+ assertThat(expr, is(notNullValue()));
+ assertThat(expr.getSelectExpression(), is("/a/b/c"));
+ assertThat(expr.getOutputExpression(),
is(SequencerPathExpression.DEFAULT_OUTPUT_EXPRESSION));
+ }
+
+ @Test( expected = InvalidSequencerPathExpression.class )
+ public void
shouldNotCompileExpressionWithSelectionExpressionAndDelimiterAndNoOutputExpression() {
+ SequencerPathExpression.compile("/a/b/c=>");
+ }
+
+ @Test
+ public void
shouldCompileExpressionWithSelectionExpressionAndDelimiterAndOutputExpression() {
+ expr = SequencerPathExpression.compile("/a/b/c=>.");
+ assertThat(expr, is(notNullValue()));
+ assertThat(expr.getSelectExpression(), is("/a/b/c"));
+ assertThat(expr.getOutputExpression(), is("."));
+
+ expr = SequencerPathExpression.compile("/a/b/c=>/x/y");
+ assertThat(expr, is(notNullValue()));
+ assertThat(expr.getSelectExpression(), is("/a/b/c"));
+ assertThat(expr.getOutputExpression(), is("/x/y"));
+ }
+
+ @Test
+ public void shouldCompileExpressionWithExtraWhitespace() {
+ expr = SequencerPathExpression.compile(" /a/b/c => . ");
+ assertThat(expr, is(notNullValue()));
+ assertThat(expr.getSelectExpression(), is("/a/b/c"));
+ assertThat(expr.getOutputExpression(), is("."));
+
+ expr = SequencerPathExpression.compile(" /a/b/c => /x/y ");
+ assertThat(expr, is(notNullValue()));
+ assertThat(expr.getSelectExpression(), is("/a/b/c"));
+ assertThat(expr.getOutputExpression(), is("/x/y"));
+ }
+
+ @Test
+ public void shouldCompileExpressionWithIndexes() {
+ assertThat(SequencerPathExpression.compile("/a/b[0]/c[1]/d/e"),
is(notNullValue()));
+ assertThat(SequencerPathExpression.compile("/a/b[0]/c[1]/d/e[2]"),
is(notNullValue()));
+ }
+
+ @Test
+ public void shouldNotRemoveUsedPredicates() {
+ assertThat(expr.removeUnusedPredicates("/a/b/c"),
is("/a/b/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[0]/c"),
is("/a/b[0]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[1]/c"),
is("/a/b[1]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[10]/c"),
is("/a/b[10]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[100]/c"),
is("/a/b[100]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[1000]/c"),
is("/a/b[1000]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[]/c"),
is("/a/b[]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[*]/c"),
is("/a/b[*]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[1,2]/c"),
is("/a/b[1,2]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[1,2,3,4,5]/c"),
is("/a/b[1,2,3,4,5]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b/c[@title]"),
is("/a/b/c[@title]"));
+ assertThat(expr.removeUnusedPredicates("/a/b/c[d/e/@title]"),
is("/a/b/c[d/e/@title]"));
+
assertThat(expr.removeUnusedPredicates("/a/(b/c)[(d|e)/(f|g)/@something]"),
is("/a/(b/c)[(d|e)/(f|g)/@something]"));
+ // These are legal, but aren't really useful ...
+ assertThat(expr.removeUnusedPredicates("/a/b[1][2][3]/c"),
is("/a/b[1][2][3]/c"));
+ }
+
+ @Test
+ public void shouldRemoveUnusedPredicates() {
+ assertThat(expr.removeUnusedPredicates("/a/b[-1]/c"),
is("/a/b/c"));
+
assertThat(expr.removeUnusedPredicates("/a/b[@name='wacky']/c"),
is("/a/b/c"));
+
assertThat(expr.removeUnusedPredicates("/a/b[3][@name='wacky']/c"),
is("/a/b[3]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[3][@name]/c"),
is("/a/b[3]/c"));
+ assertThat(expr.removeUnusedPredicates("/a/b[length(@name)=3]/c"),
is("/a/b/c"));
+ }
+
+ @Test
+ public void shouldRemoveAllPredicates() {
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b/c"),
is("/a/b/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[0]/c"),
is("/a/b[0]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[1]/c"),
is("/a/b[1]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[10]/c"),
is("/a/b[10]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[100]/c"),
is("/a/b[100]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[1000]/c"),
is("/a/b[1000]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[]/c"),
is("/a/b[]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[*]/c"),
is("/a/b[*]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b/c[@title]"),
is("/a/b/c"));
+ // These are legal, but aren't really useful ...
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[1][2][3]/c"),
is("/a/b[1][2][3]/c"));
+
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[-1]/c"),
is("/a/b/c"));
+
assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[@name='wacky']/c"),
is("/a/b/c"));
+
assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[3][@name='wacky']/c"),
is("/a/b[3]/c"));
+ assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[3][@name]/c"),
is("/a/b[3]/c"));
+
assertThat(expr.removeAllPredicatesExceptIndexes("/a/b[length(@name)=3]/c"),
is("/a/b/c"));
+ }
+
+ @Test
+ public void shouldReplaceAllXPathPatterns() {
+ assertThat(expr.replaceXPathPatterns("/a/b[3]/c"),
is("/a/b\\[3\\]/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[*]/c"),
is("/a/b(?:\\[\\d+\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[]/c"),
is("/a/b(?:\\[\\d+\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[0]/c"),
is("/a/b(?:\\[0\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[0,1,2,4]/c"),
is("/a/b(?:\\[(?:1|2|4)\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[1,2,4,0]/c"),
is("/a/b(?:\\[(?:1|2|4)\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[1,2,0,4]/c"),
is("/a/b(?:\\[(?:1|2|4)\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[0,1,2,0,4,0]/c"),
is("/a/b(?:\\[(?:1|2|4)\\])?/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[1,2,4]/c"),
is("/a/b\\[(?:1|2|4)\\]/c"));
+ assertThat(expr.replaceXPathPatterns("/a/b[@param]"),
is("/a/b/@param"));
+ assertThat(expr.replaceXPathPatterns("/a/b[3][@param]"),
is("/a/b\\[3\\]/@param"));
+ assertThat(expr.replaceXPathPatterns("/a/b[c/d/@param]"),
is("/a/b/c/d/@param"));
+
+ assertThat(expr.replaceXPathPatterns("/a/(b|c|d)/e"),
is("/a/(b|c|d)/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(b||c|d)/e"),
is("/a/(b|c|d)/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(b|||c|d)/e"),
is("/a/(b|c|d)/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(|b|c|d)/e"),
is("/a(/(b|c|d))?/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(b|c|d|)/e"),
is("/a(/(b|c|d))?/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(b|c|d)[]/e"),
is("/a/(b|c|d)(?:\\[\\d+\\])?/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(b|c[2]|d[])/e"),
is("/a/(b|c\\[2\\]|d(?:\\[\\d+\\])?)/e"));
+ assertThat(expr.replaceXPathPatterns("/a/(b|c/d|e)/f"),
is("/a/(b|c/d|e)/f"));
+
assertThat(expr.replaceXPathPatterns("/a/(b/c)[(d|e)/(f|g)/@something]"),
is("/a/(b/c)/(d|e)/(f|g)/@something"));
+
+ assertThat(expr.replaceXPathPatterns("/a/*/f"),
is("/a/[^/]*/f"));
+ assertThat(expr.replaceXPathPatterns("/a//f"),
is("/a(/[^/]*)*/f"));
+ assertThat(expr.replaceXPathPatterns("/a///f"),
is("/a(/[^/]*)*/f"));
+ assertThat(expr.replaceXPathPatterns("/a/////f"),
is("/a(/[^/]*)*/f"));
+ }
+
+ protected void assertNotMatches( SequencerPathExpression.Matcher matcher ) {
+ assertThat(matcher, is(notNullValue()));
+ assertThat(matcher.getSelectedPath(), is(nullValue()));
+ assertThat(matcher.getOutputPath(), is(nullValue()));
+ assertThat(matcher.matches(), is(false));
+ }
+
+ protected void assertMatches( SequencerPathExpression.Matcher matcher, String
selectedPath, String outputPath ) {
+ assertThat(matcher, is(notNullValue()));
+ assertThat(matcher.getSelectedPath(), is(selectedPath));
+ assertThat(matcher.getOutputPath(), is(outputPath));
+ if (selectedPath == null) {
+ assertThat(matcher.matches(), is(false));
+ } else {
+ assertThat(matcher.matches(), is(true));
+ }
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithoutRegardToCase() {
+ expr = SequencerPathExpression.compile("/a/b/c/d/e[@something] =>
.");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c/d/e", "/a/b/c/d/e");
+ assertMatches(expr.matcher("/a/b/c/d/E/@something"),
"/a/b/c/d/E", "/a/b/c/d/E");
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithExactFullPath() {
+ expr = SequencerPathExpression.compile("/a/b/c/d/e[@something] =>
.");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c/d/e", "/a/b/c/d/e");
+ assertNotMatches(expr.matcher("/a/b/c/d/E/@something2"));
+ assertNotMatches(expr.matcher("/a/b/c/d/ex/@something"));
+ assertNotMatches(expr.matcher("/a/b[1]/c/d/e/@something"));
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithExactFullPathAndExtraPathInsideMatch() {
+ expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] =>
.");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/a/b/c");
+ assertNotMatches(expr.matcher("/a/b/c/d/E/@something2"));
+ assertNotMatches(expr.matcher("/a/b/c/d/ex/@something"));
+ assertNotMatches(expr.matcher("/a/b[1]/c/d/e/@something"));
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithWildcardSelection() {
+ expr = SequencerPathExpression.compile("/a/*/c[d/e/@something] =>
.");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/a/b/c");
+ assertMatches(expr.matcher("/a/b[2]/c/d/e/@something"),
"/a/b[2]/c", "/a/b[2]/c");
+ assertMatches(expr.matcher("/a/rt/c/d/e/@something"),
"/a/rt/c", "/a/rt/c");
+ assertNotMatches(expr.matcher("/ac/d/e/@something"));
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithSegmentWildcardSelection() {
+ expr = SequencerPathExpression.compile("/a//c[d/e/@something] =>
.");
+ assertMatches(expr.matcher("/a/c/d/e/@something"), "/a/c",
"/a/c");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/a/b/c");
+ assertMatches(expr.matcher("/a/b[2]/c/d/e/@something"),
"/a/b[2]/c", "/a/b[2]/c");
+ assertMatches(expr.matcher("/a/rt/c/d/e/@something"),
"/a/rt/c", "/a/rt/c");
+ assertMatches(expr.matcher("/a/r/s/t/c/d/e/@something"),
"/a/r/s/t/c", "/a/r/s/t/c");
+ assertMatches(expr.matcher("/a/r[1]/s[2]/t[33]/c/d/e/@something"),
"/a/r[1]/s[2]/t[33]/c", "/a/r[1]/s[2]/t[33]/c");
+ assertNotMatches(expr.matcher("/a[3]/c/d/e/@something"));
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithIndexesInSelectionPaths() {
+ expr = SequencerPathExpression.compile("/a/b[2,3,4,5]/c/d/e[@something]
=> /x/y");
+ assertMatches(expr.matcher("/a/b[2]/c/d/e/@something"),
"/a/b[2]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[3]/c/d/e/@something"),
"/a/b[3]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[4]/c/d/e/@something"),
"/a/b[4]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[5]/c/d/e/@something"),
"/a/b[5]/c/d/e", "/x/y");
+ assertNotMatches(expr.matcher("/a/b[1]/c/d/e/@something"));
+ assertNotMatches(expr.matcher("/a/b/c/d/e/@something"));
+ assertNotMatches(expr.matcher("/a[1]/b/c/d/e/@something"));
+
+ expr = SequencerPathExpression.compile("/a/b[0,2,3,4,5]/c/d/e[@something]
=> /x/y");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[2]/c/d/e/@something"),
"/a/b[2]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[3]/c/d/e/@something"),
"/a/b[3]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[4]/c/d/e/@something"),
"/a/b[4]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[5]/c/d/e/@something"),
"/a/b[5]/c/d/e", "/x/y");
+ assertNotMatches(expr.matcher("/a/b[1]/c/d/e/@something"));
+ assertNotMatches(expr.matcher("/a[1]/b/c/d/e/@something"));
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithAnyIndexesInSelectionPaths() {
+ expr = SequencerPathExpression.compile("/a/b[*]/c[]/d/e[@something] =>
/x/y");
+ assertMatches(expr.matcher("/a/b[2]/c/d/e/@something"),
"/a/b[2]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[3]/c/d/e/@something"),
"/a/b[3]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[4]/c/d/e/@something"),
"/a/b[4]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[5]/c/d/e/@something"),
"/a/b[5]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[1]/c/d/e/@something"),
"/a/b[1]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[6]/c/d/e/@something"),
"/a/b[6]/c/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b[6]/c[1]/d/e/@something"),
"/a/b[6]/c[1]/d/e", "/x/y");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c/d/e", "/x/y");
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithFullOutputPath() {
+ expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] =>
/x/y");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/x/y");
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithRepositoryInSelectionPath() {
+ expr = SequencerPathExpression.compile("reposA:/a/b/c[d/e/@something] =>
/x/y");
+ assertMatches(expr.matcher("reposA:/a/b/c/d/e/@something"),
"reposA:/a/b/c", "/x/y");
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithRepositoryInFullOutputPath() {
+ expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] =>
reposA:/x/y");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "reposA:/x/y");
+ }
+
+ @Test
+ public void shouldMatchExpressionsWithNamedGroupsInOutputPath() {
+ expr = SequencerPathExpression.compile("/a(//c)[d/e/@something] =>
$1/y/z");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/b/c/y/z");
+
+ expr = SequencerPathExpression.compile("/a(/(b|c|d|)/e)[f/g/@something]
=> $1/y/z");
+ assertMatches(expr.matcher("/a/b/e/f/g/@something"),
"/a/b/e", "/b/e/y/z");
+ assertMatches(expr.matcher("/a/c/e/f/g/@something"),
"/a/c/e", "/c/e/y/z");
+ assertMatches(expr.matcher("/a/d/e/f/g/@something"),
"/a/d/e", "/d/e/y/z");
+ assertMatches(expr.matcher("/a/e/f/g/@something"), "/a/e",
"/e/y/z");
+ assertNotMatches(expr.matcher("/a/t/e/f/g/@something"));
+
+ expr = SequencerPathExpression.compile("/a/(b/c)[(d|e)/(f|g)/@something]
=> /u/$1/y/z/$2/$3");
+ assertMatches(expr.matcher("/a/b/c/d/f/@something"),
"/a/b/c", "/u/b/c/y/z/d/f");
+ assertMatches(expr.matcher("/a/b/c/e/f/@something"),
"/a/b/c", "/u/b/c/y/z/e/f");
+ assertMatches(expr.matcher("/a/b/c/d/g/@something"),
"/a/b/c", "/u/b/c/y/z/d/g");
+ assertMatches(expr.matcher("/a/b/c/e/g/@something"),
"/a/b/c", "/u/b/c/y/z/e/g");
+
+ expr = SequencerPathExpression.compile("/a/(b/c)/(d|e)/(f|g)/@something
=> /u/$1/y/z/$2/$3");
+ assertMatches(expr.matcher("/a/b/c/d/f/@something"),
"/a/b/c/d/f", "/u/b/c/y/z/d/f");
+ assertMatches(expr.matcher("/a/b/c/e/f/@something"),
"/a/b/c/e/f", "/u/b/c/y/z/e/f");
+ assertMatches(expr.matcher("/a/b/c/d/g/@something"),
"/a/b/c/d/g", "/u/b/c/y/z/d/g");
+ assertMatches(expr.matcher("/a/b/c/e/g/@something"),
"/a/b/c/e/g", "/u/b/c/y/z/e/g");
+ }
+
+ @Test
+ public void shouldMatchExpressionWithReoccurringNamedGroupsDollarsInOutputPath() {
+ expr = SequencerPathExpression.compile("/a/(b/c)[(d|e)/(f|g)/@something]
=> /u/$1/y/z/$2/$3/$1/$1");
+ assertMatches(expr.matcher("/a/b/c/d/f/@something"),
"/a/b/c", "/u/b/c/y/z/d/f/b/c/b/c");
+ }
+
+ @Test
+ public void shouldMatchExpressionWithNamedGroupsAndEscapedDollarsInOutputPath() {
+ expr = SequencerPathExpression.compile("/a/(b/c)[(d|e)/(f|g)/@something]
=> /\\$2u/$1/y/z/$2/$3");
+ assertMatches(expr.matcher("/a/b/c/d/f/@something"),
"/a/b/c", "/\\$2u/b/c/y/z/d/f");
+ }
+
+ @Test
+ public void shouldMatchExpressionWithParentReferencesInOutputPath() {
+ expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] =>
/x/y/z/../..");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/x");
+
+ expr = SequencerPathExpression.compile("/a/(b/c)[d/e/@something] =>
/x/$1/z/../../v");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/x/b/v");
+ }
+
+ @Test
+ public void shouldMatchExpressionWithSelfReferencesInOutputPath() {
+ expr = SequencerPathExpression.compile("/a/b/c[d/e/@something] =>
/x/y/./z/.");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/x/y/z");
+
+ expr = SequencerPathExpression.compile("/a/(b/c)[d/e/@something] =>
/x/$1/./z");
+ assertMatches(expr.matcher("/a/b/c/d/e/@something"),
"/a/b/c", "/x/b/c/z");
+ }
+
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencingServiceTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencingServiceTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/SequencingServiceTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,331 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+
+package org.jboss.dna.repository.sequencers;
+
+import java.util.concurrent.TimeUnit;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.matchers.JUnitMatchers.hasItem;
+import javax.jcr.Node;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import org.jboss.dna.common.jcr.AbstractJcrRepositoryTest;
+import org.jboss.dna.repository.observation.ObservationService;
+import org.jboss.dna.repository.sequencers.Sequencer;
+import org.jboss.dna.repository.sequencers.SequencerConfig;
+import org.jboss.dna.repository.sequencers.SequencingService;
+import org.jboss.dna.repository.services.ServiceAdministrator;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.SimpleExecutionContext;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class SequencingServiceTest extends AbstractJcrRepositoryTest {
+
+ public static final int ALL_EVENT_TYPES = Event.NODE_ADDED | Event.NODE_REMOVED |
Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+ public static final String REPOSITORY_WORKSPACE_NAME =
"testRepository-Workspace";
+
+ private ObservationService observationService;
+ private SequencingService sequencingService;
+ private ExecutionContext executionContext;
+
+ @Before
+ public void beforeEach() throws Exception {
+ this.executionContext = new SimpleExecutionContext(this,
REPOSITORY_WORKSPACE_NAME);
+ this.sequencingService = new SequencingService();
+ this.sequencingService.setExecutionContext(this.executionContext);
+ this.observationService = new
ObservationService(this.executionContext.getSessionFactory());
+ this.observationService.addListener(this.sequencingService);
+ }
+
+ @After
+ public void afterEach() throws Exception {
+ super.shutdownRepository();
+ this.sequencingService.getAdministrator().shutdown();
+ }
+
+ @Test
+ public void shouldHaveTheDefaultSelectorUponConstruction() {
+ assertThat(sequencingService.getSequencerSelector(),
is(sameInstance(SequencingService.DEFAULT_SEQUENCER_SELECTOR)));
+ }
+
+ @Test
+ public void shouldHaveNoExecutorServiceUponConstruction() {
+ assertThat(sequencingService.getExecutorService(), is(nullValue()));
+ }
+
+ @Test
+ public void
shouldCreateDefaultExecutorServiceWhenStartedIfNoExecutorServiceHasBeenSet() {
+ assertThat(sequencingService.getExecutorService(), is(nullValue()));
+ sequencingService.getAdministrator().start();
+ assertThat(sequencingService.getExecutorService(), is(notNullValue()));
+ }
+
+ @Test
+ public void shouldCreateExecutorServiceWhenStarted() {
+ assertThat(sequencingService.getExecutorService(), is(nullValue()));
+ sequencingService.getAdministrator().start();
+ assertThat(sequencingService.getExecutorService(), is(notNullValue()));
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailToSetStateToUnknownString() {
+ sequencingService.getAdministrator().setState("asdf");
+ }
+
+ @Test( expected = IllegalArgumentException.class )
+ public void shouldFailToSetStateToNullString() {
+ sequencingService.getAdministrator().setState((String)null);
+ }
+
+ @Test
+ public void shouldSetStateUsingLowercaseString() {
+
assertThat(sequencingService.getAdministrator().setState("started").isStarted(),
is(true));
+
assertThat(sequencingService.getAdministrator().setState("paused").isPaused(),
is(true));
+
assertThat(sequencingService.getAdministrator().setState("shutdown").isShutdown(),
is(true));
+ }
+
+ @Test
+ public void shouldSetStateUsingMixedCaseString() {
+
assertThat(sequencingService.getAdministrator().setState("StarTeD").isStarted(),
is(true));
+
assertThat(sequencingService.getAdministrator().setState("PauSed").isPaused(),
is(true));
+
assertThat(sequencingService.getAdministrator().setState("ShuTDowN").isShutdown(),
is(true));
+ }
+
+ @Test
+ public void shouldSetStateUsingUppercasString() {
+
assertThat(sequencingService.getAdministrator().setState("STARTED").isStarted(),
is(true));
+
assertThat(sequencingService.getAdministrator().setState("PAUSED").isPaused(),
is(true));
+
assertThat(sequencingService.getAdministrator().setState("SHUTDOWN").isShutdown(),
is(true));
+ }
+
+ @Test
+ public void shouldBePausedUponConstruction() {
+ assertThat(sequencingService.getAdministrator().isPaused(), is(true));
+ assertThat(sequencingService.getAdministrator().getState(),
is(ServiceAdministrator.State.PAUSED));
+ assertThat(sequencingService.getAdministrator().isShutdown(), is(false));
+ assertThat(sequencingService.getAdministrator().isStarted(), is(false));
+ }
+
+ @Test
+ public void shouldBeAbleToShutdownWhenNotStarted() {
+ assertThat(sequencingService.getAdministrator().isShutdown(), is(false));
+ for (int i = 0; i != 3; ++i) {
+ assertThat(sequencingService.getAdministrator().shutdown().isShutdown(),
is(true));
+ assertThat(sequencingService.getAdministrator().isPaused(), is(false));
+ assertThat(sequencingService.getAdministrator().isStarted(), is(false));
+ assertThat(sequencingService.getAdministrator().getState(),
is(ServiceAdministrator.State.SHUTDOWN));
+ }
+ }
+
+ @Test
+ public void shouldBeAbleToBePauseAndRestarted() {
+ assertThat(sequencingService.getAdministrator().isShutdown(), is(false));
+ for (int i = 0; i != 3; ++i) {
+ // Now pause it ...
+ assertThat(sequencingService.getAdministrator().pause().isPaused(),
is(true));
+ assertThat(sequencingService.getAdministrator().isStarted(), is(false));
+ assertThat(sequencingService.getAdministrator().isShutdown(), is(false));
+ assertThat(sequencingService.getAdministrator().getState(),
is(ServiceAdministrator.State.PAUSED));
+
+ // Now start it back up ...
+ assertThat(sequencingService.getAdministrator().start().isStarted(),
is(true));
+ assertThat(sequencingService.getAdministrator().isPaused(), is(false));
+ assertThat(sequencingService.getAdministrator().isShutdown(), is(false));
+ assertThat(sequencingService.getAdministrator().getState(),
is(ServiceAdministrator.State.STARTED));
+ }
+ }
+
+ @Test( expected = IllegalStateException.class )
+ public void shouldNotBeAbleToBeRestartedAfterBeingShutdown() {
+ assertThat(sequencingService.getAdministrator().isShutdown(), is(false));
+ // Shut it down ...
+ assertThat(sequencingService.getAdministrator().shutdown().isShutdown(),
is(true));
+ assertThat(sequencingService.getAdministrator().isPaused(), is(false));
+ assertThat(sequencingService.getAdministrator().isStarted(), is(false));
+ assertThat(sequencingService.getAdministrator().getState(),
is(ServiceAdministrator.State.SHUTDOWN));
+
+ // Now start it back up ... this will fail
+ sequencingService.getAdministrator().start();
+ }
+
+ @Test
+ public void shouldBeAbleToMonitorWorkspaceWhenPausedOrStarted() throws Exception {
+ startRepository();
+ Session session = getRepository().login(getTestCredentials());
+
+ // Try when paused ...
+ assertThat(sequencingService.getAdministrator().isPaused(), is(true));
+ assertThat(observationService.getAdministrator().pause().isPaused(), is(true));
+ ObservationService.WorkspaceListener listener =
observationService.monitor(REPOSITORY_WORKSPACE_NAME, Event.NODE_ADDED);
+ assertThat(listener, is(notNullValue()));
+ assertThat(listener.getAbsolutePath(), is("/"));
+ assertThat(listener.getEventTypes(), is(Event.NODE_ADDED));
+
+ // Cause an event ...
+ session.getRootNode().addNode("testnodeA",
"nt:unstructured");
+ session.save();
+ Thread.sleep(100); // let the events be handled ...
+ assertThat(observationService.getStatistics().getNumberOfEventsIgnored(),
is((long)1));
+
+ // Reset the statistics and remove the listener ...
+ sequencingService.getStatistics().reset();
+ observationService.getStatistics().reset();
+ assertThat(listener.isRegistered(), is(true));
+ listener.unregister();
+ assertThat(listener.isRegistered(), is(false));
+
+ // Start the sequencing sequencingService and try monitoring the workspace ...
+ assertThat(sequencingService.getAdministrator().start().isStarted(), is(true));
+ assertThat(observationService.getAdministrator().start().isStarted(), is(true));
+ ObservationService.WorkspaceListener listener2 =
observationService.monitor(REPOSITORY_WORKSPACE_NAME, Event.NODE_ADDED);
+ assertThat(listener2.isRegistered(), is(true));
+ assertThat(listener2, is(notNullValue()));
+ assertThat(listener2.getAbsolutePath(), is("/"));
+ assertThat(listener2.getEventTypes(), is(Event.NODE_ADDED));
+
+ // Cause an event ...
+ session.getRootNode().addNode("testnodeB",
"nt:unstructured");
+ session.save();
+ Thread.sleep(100); // let the events be handled ...
+
+ // Check the results: nothing ignored, and 1 node skipped (since no sequencers
apply)
+ assertThat(observationService.getStatistics().getNumberOfEventsIgnored(),
is((long)0));
+ assertThat(sequencingService.getStatistics().getNumberOfNodesSkipped(),
is((long)1));
+
+ sequencingService.getAdministrator().shutdown();
+ sequencingService.getStatistics().reset();
+ observationService.getAdministrator().shutdown();
+ observationService.getStatistics().reset();
+
+ // Cause another event ...
+ session.getRootNode().addNode("testnodeC",
"nt:unstructured");
+ session.save();
+ Thread.sleep(100); // let the events be handled ...
+ // The listener should no longer be registered ...
+ assertThat(listener2.isRegistered(), is(false));
+
+ // Check the results: nothing ignored, and nothing skipped
+ assertThat(observationService.getStatistics().getNumberOfEventsIgnored(),
is((long)0));
+ assertThat(sequencingService.getStatistics().getNumberOfNodesSkipped(),
is((long)0));
+ }
+
+ @Test
+ public void
shouldUnregisterAllWorkspaceListenersWhenSystemIsShutdownAndNotWhenPaused() throws
Exception {
+ startRepository();
+ Session session = getRepository().login(getTestCredentials());
+
+ // Start the sequencing sequencingService and try monitoring the workspace ...
+ assertThat(sequencingService.getAdministrator().start().isStarted(), is(true));
+ ObservationService.WorkspaceListener listener =
observationService.monitor(REPOSITORY_WORKSPACE_NAME, Event.NODE_ADDED);
+ assertThat(listener.isRegistered(), is(true));
+ assertThat(listener, is(notNullValue()));
+ assertThat(listener.getAbsolutePath(), is("/"));
+ assertThat(listener.getEventTypes(), is(Event.NODE_ADDED));
+
+ // Cause an event ...
+ session.getRootNode().addNode("testnodeB",
"nt:unstructured");
+ session.save();
+ assertThat(listener.isRegistered(), is(true));
+
+ // Pause the sequencingService, can cause an event ...
+ sequencingService.getAdministrator().pause();
+ session.getRootNode().addNode("testnodeB",
"nt:unstructured");
+ session.save();
+ assertThat(listener.isRegistered(), is(true));
+
+ // Shut down the services and await termination ...
+ sequencingService.getAdministrator().shutdown();
+ observationService.getAdministrator().shutdown();
+ sequencingService.getAdministrator().awaitTermination(2, TimeUnit.SECONDS);
+ observationService.getAdministrator().awaitTermination(2, TimeUnit.SECONDS);
+
+ // Cause another event ...
+ session.getRootNode().addNode("testnodeC",
"nt:unstructured");
+ session.save();
+
+ // The listener should no longer be registered ...
+ assertThat(listener.isRegistered(), is(false));
+ }
+
+ @Test
+ public void
shouldExecuteSequencersUponChangesToRepositoryThatMatchSequencerPathExpressions() throws
Exception {
+ // Add configurations for a sequencer ...
+ String name = "MockSequencerA";
+ String desc = "A mock sequencer that accumulates the number of times
it's called";
+ String classname = MockSequencerA.class.getName();
+ String[] classpath = null;
+ String[] pathExpressions = {"/testnodeC/testnodeD/@description =>
."};
+ SequencerConfig configA = new SequencerConfig(name, desc, classname, classpath,
pathExpressions);
+ sequencingService.addSequencer(configA);
+
+ // Start the repository and get a session ...
+ startRepository();
+ Session session = getRepository().login(getTestCredentials());
+
+ // Start the sequencing sequencingService and try monitoring the workspace ...
+ assertThat(sequencingService.getAdministrator().start().isStarted(), is(true));
+ ObservationService.WorkspaceListener listener =
observationService.monitor(REPOSITORY_WORKSPACE_NAME, ALL_EVENT_TYPES);
+ assertThat(listener.isRegistered(), is(true));
+ assertThat(listener, is(notNullValue()));
+ assertThat(listener.getAbsolutePath(), is("/"));
+ assertThat(listener.getEventTypes(), is(ALL_EVENT_TYPES));
+
+ // The sequencer should not yet have run ...
+ MockSequencerA sequencerA =
(MockSequencerA)sequencingService.getSequencerLibrary().getInstances().get(0);
+ assertThat(sequencerA, is(notNullValue()));
+ assertThat(sequencerA.getCounter(), is(0));
+ assertThat(sequencingService.getSequencerLibrary().getInstances(),
hasItem((Sequencer)sequencerA));
+
+ // Cause an event, but not one that the sequencer cares about ...
+ Node nodeC = session.getRootNode().addNode("testnodeC",
"nt:unstructured");
+ assertThat(nodeC, is(notNullValue()));
+ session.save();
+ assertThat(sequencerA.getCounter(), is(0));
+ assertThat(sequencingService.getSequencerLibrary().getInstances(),
hasItem((Sequencer)sequencerA));
+
+ // Cause another event, but again one that the sequencer does not care about ...
+ Node nodeD = nodeC.addNode("testnodeD", "nt:unstructured");
+ assertThat(nodeD, is(notNullValue()));
+ session.save();
+ assertThat(sequencerA.getCounter(), is(0));
+ assertThat(sequencingService.getSequencerLibrary().getInstances(),
hasItem((Sequencer)sequencerA));
+
+ // Now set the property that the sequencer DOES care about ...
+ sequencerA.setExpectedCount(1);
+ nodeD.setProperty("description", "This is the value");
+ session.save();
+
+ // Wait for the event to be processed and the sequencer to be called ...
+ sequencerA.awaitExecution(4, TimeUnit.SECONDS); // wait for the sequencer to be
called
+ assertThat(sequencerA.getCounter(), is(1));
+ assertThat(sequencingService.getSequencerLibrary().getInstances(),
hasItem((Sequencer)sequencerA));
+ }
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapterTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapterTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/sequencers/StreamSequencerAdapterTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,336 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.sequencers;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import javax.jcr.Node;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import org.jboss.dna.common.jcr.AbstractJcrRepositoryTest;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+import org.jboss.dna.common.monitor.RecordingProgressMonitor;
+import org.jboss.dna.repository.observation.NodeChange;
+import org.jboss.dna.repository.sequencers.SequencerConfig;
+import org.jboss.dna.repository.sequencers.SequencerException;
+import org.jboss.dna.repository.sequencers.SequencerOutputMap;
+import org.jboss.dna.repository.sequencers.StreamSequencerAdapter;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.JcrTools;
+import org.jboss.dna.repository.util.RepositoryNodePath;
+import org.jboss.dna.repository.util.SessionFactory;
+import org.jboss.dna.spi.sequencers.SequencerOutput;
+import org.jboss.dna.spi.sequencers.StreamSequencer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class StreamSequencerAdapterTest extends AbstractJcrRepositoryTest {
+
+ private StreamSequencer streamSequencer;
+ private StreamSequencerAdapter sequencer;
+ private String[] validExpressions = {"/a/* => /output"};
+ private SequencerConfig validConfig = new SequencerConfig("name",
"desc", "something.class", null, validExpressions);
+ private JcrTools tools;
+ private Session session;
+ private SequencerOutputMap sequencerOutput;
+ private String sampleData = "The little brown fox didn't something
bad.";
+ private ExecutionContext context;
+ private RecordingProgressMonitor progressMonitor;
+ private String repositoryWorkspaceName = "something";
+
+ @Before
+ public void beforeEach() throws Exception {
+ final JcrTools tools = new JcrTools();
+ this.tools = tools;
+ sequencerOutput = new SequencerOutputMap();
+ progressMonitor = new
RecordingProgressMonitor(StreamSequencerAdapterTest.class.getName());
+ final SessionFactory sessionFactory = new SessionFactory() {
+
+ public Session createSession( String name ) {
+ return createTestSession();
+ }
+ };
+ context = new ExecutionContext() {
+
+ public SessionFactory getSessionFactory() {
+ return sessionFactory;
+ }
+
+ public JcrTools getTools() {
+ return tools;
+ }
+ };
+ final SequencerOutputMap finalOutput = sequencerOutput;
+ streamSequencer = new StreamSequencer() {
+
+ /**
+ * This method always copies the {@link
StreamSequencerAdapterTest#sequencerOutput} data into the output {@inheritDoc},
+ * and does nothing else with any of the other parameters.
+ */
+ public void sequence( InputStream stream, SequencerOutput output,
ProgressMonitor progressMonitor ) {
+ for (SequencerOutputMap.Entry entry : finalOutput) {
+ String nodePath = entry.getPath().getString();
+ for (SequencerOutputMap.PropertyValue property :
entry.getPropertyValues()) {
+ output.setProperty(nodePath, property.getName(),
property.getValue());
+ }
+ }
+ }
+ };
+ sequencer = new StreamSequencerAdapter(streamSequencer);
+ }
+
+ @After
+ public void afterEach() throws Exception {
+ if (session != null) {
+ try {
+ session.logout();
+ } finally {
+ session = null;
+ }
+ }
+ }
+
+ protected Session createTestSession() {
+ try {
+ return getRepository().login(getTestCredentials());
+ } catch (Exception e) {
+ fail("Unable to create repository session: " + e.getMessage());
+ return null; // won't get here
+ }
+ }
+
+ @Test
+ public void shouldNotHaveSequencerUponInstantiation() {
+ assertThat(sequencer.getConfiguration(), is(nullValue()));
+ sequencer.setConfiguration(validConfig);
+ assertThat(sequencer.getConfiguration(), is(sameInstance(validConfig)));
+ }
+
+ @Test
+ public void shouldExtractNullMixinTypesFromNullValue() {
+ assertThat(sequencer.extractMixinTypes(null), is(nullValue()));
+ }
+
+ @Test
+ public void shouldExtractMixinTypesFromStringValue() {
+ assertThat(sequencer.extractMixinTypes("value"), is(new String[]
{"value"}));
+ assertThat(sequencer.extractMixinTypes(""), is(new String[]
{""}));
+ }
+
+ @Test
+ public void shouldExtractMixinTypesFromStringArrayValue() {
+ assertThat(sequencer.extractMixinTypes(new String[] {"value1"}), is(new
String[] {"value1"}));
+ assertThat(sequencer.extractMixinTypes(new String[] {"value1",
"value2"}), is(new String[] {"value1", "value2"}));
+ }
+
+ @Test
+ public void shouldExtractMixinTypesFromStringArrayWithNullValue() {
+ assertThat(sequencer.extractMixinTypes(new String[] {"value1", null,
"value2"}), is(new String[] {"value1", null, "value2"}));
+ }
+
+ @Test
+ public void shouldExecuteSequencerOnExistingNodeAndOutputToExistingNode() throws
Exception {
+ startRepository();
+ session = getRepository().login(getTestCredentials());
+
+ // Set up the repository for the test ...
+ Node nodeC = tools.findOrCreateNode(session, "/a/b/c");
+ Node nodeE = tools.findOrCreateNode(session, "/d/e");
+ assertThat(nodeC, is(notNullValue()));
+ assertThat(nodeE, is(notNullValue()));
+ assertThat(nodeE.getNodes().getSize(), is(0l));
+ assertThat(nodeE.getProperties().getSize(), is(1l)); // jcr:primaryType
+ assertThat(nodeE.getProperty("jcr:primaryType").getString(),
is("nt:unstructured"));
+
+ // Set the property that will be sequenced ...
+ nodeC.setProperty("sequencedProperty", new
ByteArrayInputStream(sampleData.getBytes()));
+
+ // Set up the node changes ...
+ NodeChange nodeChange = new NodeChange(repositoryWorkspaceName, nodeC.getPath(),
Event.PROPERTY_CHANGED, Collections.singleton("sequencedProperty"), null);
+
+ // Set up the output directory ...
+ Set<RepositoryNodePath> outputPaths = new
HashSet<RepositoryNodePath>();
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
nodeE.getPath()));
+
+ // Generate the output data that the sequencer subclass will produce and that
should be saved to the repository ...
+ sequencerOutput.setProperty("alpha/beta", "isSomething",
true);
+
+ // Call the sequencer ...
+ sequencer.execute(nodeC, "sequencedProperty", nodeChange, outputPaths,
context, progressMonitor);
+ }
+
+ @Test( expected = SequencerException.class )
+ public void
shouldExecuteSequencerOnExistingNodeWithMissingSequencedPropertyAndOutputToExistingNode()
throws Exception {
+ startRepository();
+ session = getRepository().login(getTestCredentials());
+
+ // Set up the repository for the test ...
+ Node nodeC = tools.findOrCreateNode(session, "/a/b/c");
+ Node nodeE = tools.findOrCreateNode(session, "/d/e");
+ assertThat(nodeC, is(notNullValue()));
+ assertThat(nodeE, is(notNullValue()));
+ assertThat(nodeE.getNodes().getSize(), is(0l));
+ assertThat(nodeE.getProperties().getSize(), is(1l)); // jcr:primaryType
+ assertThat(nodeE.getProperty("jcr:primaryType").getString(),
is("nt:unstructured"));
+
+ // Set the property that will be sequenced ...
+ // THIS TEST REQUIRES THIS PROPERTY TO BE NULL OR NON-EXISTANT
+ nodeC.setProperty("sequencedProperty", (InputStream)null);
+
+ // Set up the node changes ...
+ NodeChange nodeChange = new NodeChange(repositoryWorkspaceName, nodeC.getPath(),
Event.PROPERTY_CHANGED, Collections.singleton("sequencedProperty"), null);
+
+ // Set up the output directory ...
+ Set<RepositoryNodePath> outputPaths = new
HashSet<RepositoryNodePath>();
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
nodeE.getPath()));
+
+ // Generate the output data that the sequencer subclass will produce and that
should be saved to the repository ...
+ sequencerOutput.setProperty("alpha/beta", "isSomething",
true);
+
+ // Call the sequencer, which should cause the exception ...
+ sequencer.execute(nodeC, "sequencedProperty", nodeChange, outputPaths,
context, progressMonitor);
+ }
+
+ @Test
+ public void shouldExecuteSequencerOnExistingNodeAndOutputToMultipleExistingNodes()
throws Exception {
+ startRepository();
+ session = getRepository().login(getTestCredentials());
+
+ // Set up the repository for the test ...
+ Node nodeC = tools.findOrCreateNode(session, "/a/b/c");
+ Node nodeE = tools.findOrCreateNode(session, "/d/e");
+ assertThat(nodeC, is(notNullValue()));
+ assertThat(nodeE, is(notNullValue()));
+ assertThat(nodeE.getNodes().getSize(), is(0l));
+ assertThat(nodeE.getProperties().getSize(), is(1l)); // jcr:primaryType
+ assertThat(nodeE.getProperty("jcr:primaryType").getString(),
is("nt:unstructured"));
+
+ // Set the property that will be sequenced ...
+ nodeC.setProperty("sequencedProperty", new
ByteArrayInputStream(sampleData.getBytes()));
+
+ // Set up the node changes ...
+ NodeChange nodeChange = new NodeChange(repositoryWorkspaceName, nodeC.getPath(),
Event.PROPERTY_CHANGED, Collections.singleton("sequencedProperty"), null);
+
+ // Set up the output directory ...
+ Set<RepositoryNodePath> outputPaths = new
HashSet<RepositoryNodePath>();
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
"/d/e"));
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
"/x/y/z"));
+
+ // Generate the output data that the sequencer subclass will produce and that
should be saved to the repository ...
+ sequencerOutput.setProperty("alpha/beta", "isSomething",
true);
+
+ // Call the sequencer ...
+ sequencer.execute(nodeC, "sequencedProperty", nodeChange, outputPaths,
context, progressMonitor);
+
+ // Check to see that the output nodes have been created ...
+ assertThat(session.getRootNode().hasNode("d/e"), is(true));
+ assertThat(session.getRootNode().hasNode("x/y/z"), is(true));
+ }
+
+ @Test
+ public void shouldExecuteSequencerOnExistingNodeAndOutputToNonExistingNode() throws
Exception {
+ startRepository();
+ session = getRepository().login(getTestCredentials());
+
+ // Set up the repository for the test ...
+ Node nodeC = tools.findOrCreateNode(session, "/a/b/c");
+ assertThat(session.getRootNode().hasNode("d"), is(false));
+ assertThat(nodeC, is(notNullValue()));
+
+ // Set the property that will be sequenced ...
+ nodeC.setProperty("sequencedProperty", new
ByteArrayInputStream(sampleData.getBytes()));
+
+ // Set up the node changes ...
+ NodeChange nodeChange = new NodeChange(repositoryWorkspaceName, nodeC.getPath(),
Event.PROPERTY_CHANGED, Collections.singleton("sequencedProperty"), null);
+
+ // Set up the output directory ...
+ Set<RepositoryNodePath> outputPaths = new
HashSet<RepositoryNodePath>();
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
"/d/e"));
+
+ // Generate the output data that the sequencer subclass will produce and that
should be saved to the repository ...
+ sequencerOutput.setProperty("alpha/beta", "isSomething",
true);
+
+ // Call the sequencer ...
+ sequencer.execute(nodeC, "sequencedProperty", nodeChange, outputPaths,
context, progressMonitor);
+
+ // Check to see that the "/d/e" node has been created ...
+ assertThat(session.getRootNode().hasNode("d/e"), is(true));
+ }
+
+ @Test
+ public void shouldExecuteSequencerOnExistingNodeAndOutputToMultipleNonExistingNodes()
throws Exception {
+ startRepository();
+ session = getRepository().login(getTestCredentials());
+
+ // Set up the repository for the test ...
+ Node nodeC = tools.findOrCreateNode(session, "/a/b/c");
+ assertThat(session.getRootNode().hasNode("d"), is(false));
+ assertThat(session.getRootNode().hasNode("x"), is(false));
+ assertThat(nodeC, is(notNullValue()));
+
+ // Set the property that will be sequenced ...
+ nodeC.setProperty("sequencedProperty", new
ByteArrayInputStream(sampleData.getBytes()));
+
+ // Set up the node changes ...
+ NodeChange nodeChange = new NodeChange(repositoryWorkspaceName, nodeC.getPath(),
Event.PROPERTY_CHANGED, Collections.singleton("sequencedProperty"), null);
+
+ // Set up the output directory ...
+ Set<RepositoryNodePath> outputPaths = new
HashSet<RepositoryNodePath>();
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
"/d/e"));
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
"/x/y/z"));
+ outputPaths.add(new RepositoryNodePath(repositoryWorkspaceName,
"/x/z"));
+
+ // Generate the output data that the sequencer subclass will produce and that
should be saved to the repository ...
+ sequencerOutput.setProperty("alpha/beta", "isSomething",
true);
+
+ // Call the sequencer ...
+ sequencer.execute(nodeC, "sequencedProperty", nodeChange, outputPaths,
context, progressMonitor);
+
+ // Check to see that the output nodes have been created ...
+ assertThat(session.getRootNode().hasNode("d/e"), is(true));
+ assertThat(session.getRootNode().hasNode("x/y/z"), is(true));
+ assertThat(session.getRootNode().hasNode("x/z"), is(true));
+
+ // Check to see that the sequencer-generated nodes have been created ...
+ // Node beta = session.getRootNode().getNode("d/e/alpha/beta");
+ // for (PropertyIterator iter = beta.getProperties(); iter.hasNext();) {
+ // Property property = iter.nextProperty();
+ // System.out.println("Property on " + beta.getPath() + " ===>
" + property.getName() + " = " + property.getValue());
+ // }
+
assertThat(session.getRootNode().getNode("d/e/alpha/beta").getProperty("isSomething").getBoolean(),
is(true));
+
assertThat(session.getRootNode().getNode("x/y/z/alpha/beta").getProperty("isSomething").getBoolean(),
is(true));
+
assertThat(session.getRootNode().getNode("x/z/alpha/beta").getProperty("isSomething").getBoolean(),
is(true));
+ }
+}
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/JndiSessionFactoryTest.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/JndiSessionFactoryTest.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/JndiSessionFactoryTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,168 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.util;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNull.notNullValue;
+import static org.hamcrest.core.IsNull.nullValue;
+import static org.hamcrest.core.IsSame.sameInstance;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import javax.jcr.Repository;
+import javax.jcr.Session;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import org.jboss.dna.common.SystemFailureException;
+import org.jboss.dna.common.jcr.AbstractJcrRepositoryTest;
+import org.jboss.dna.common.naming.MockInitialContext;
+import org.jboss.dna.repository.util.JndiSessionFactory;
+import org.jmock.Mockery;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * @author Randall Hauch
+ */
+public class JndiSessionFactoryTest extends AbstractJcrRepositoryTest {
+
+ public static final String MOCK_REPOSITORY_NAME =
"java:jcr/unit/test/repository/1";
+ public static final String MOCK_REPOSITORY_NAME_ALT =
"java:jcr/unit/test/repository/2";
+
+ private JndiSessionFactory factory;
+ private Mockery context;
+ private Repository mockRepository;
+ private Session session;
+
+ @Before
+ public void beforeEach() throws Exception {
+ this.context = new Mockery();
+ this.mockRepository = this.context.mock(Repository.class);
+ MockInitialContext.register(MOCK_REPOSITORY_NAME, this.mockRepository);
+ this.factory = new JndiSessionFactory();
+ }
+
+ @After
+ public void afterEach() throws Exception {
+ if (session != null) {
+ session.logout();
+ }
+ MockInitialContext.tearDown();
+ }
+
+ protected void assertNotRegistered( String name ) {
+ try {
+ new InitialContext().lookup(name);
+ fail("Unexpectedly found registered object");
+ } catch (NamingException e) {
+ // expected ...
+ }
+ }
+
+ protected void assertRegistered( String name, Object obj ) {
+ try {
+ assertThat(new InitialContext().lookup(name), is(sameInstance(obj)));
+ } catch (NamingException e) {
+ fail("Failed to find registered object \"" + name +
"\"");
+ }
+ }
+
+ @Test
+ public void shouldCreateSessionForRegisteredRepository() {
+
+ }
+
+ @Test( expected = SystemFailureException.class )
+ public void shouldThrowSystemFailureWhenUnableToFindRegisteredRepository() throws
Exception {
+ factory.createSession(MOCK_REPOSITORY_NAME +
"something_extra_that_can't_be_found");
+ }
+
+ @Test
+ public void shouldFindWorkspaceInRegisteredName() {
+
assertThat(factory.getWorkspaceName("java:jcr/path/to/repository/workspaceName"),
is("workspaceName"));
+ }
+
+ @Test
+ public void shouldReturnNullWorkspaceIfRegisteredNameEndsInDelimiter() {
+ assertThat(factory.getWorkspaceName("java:jcr/path/to/repository/"),
is(nullValue()));
+ }
+
+ @Test
+ public void shouldReturnNullWorkspaceIfRegisteredNameHasNoDelimiter() {
+ assertThat(factory.getWorkspaceName("java:jcr"), is(nullValue()));
+ }
+
+ @Test
+ public void shouldFindRepositoryInRegisteredName() {
+
assertThat(factory.getRepositoryName("java:jcr/path/to/repository/workspaceName"),
is("java:jcr/path/to/repository"));
+ }
+
+ @Test
+ public void
shouldConsiderWholeRegisteredNameToBeRepositoryNameIfRegisteredNameEndsInDelimiter() {
+ assertThat(factory.getRepositoryName("java:jcr/path/to/repository/"),
is("java:jcr/path/to/repository"));
+ }
+
+ @Test
+ public void
shouldConsiderWholeRegisteredNameToBeRepositoryNameIfRegisteredNameHasNoDelimiter() {
+ assertThat(factory.getRepositoryName("java:jcr"),
is("java:jcr"));
+ }
+
+ @Test
+ public void shouldRegisterSuppliedRepositoryInJndi() throws Exception {
+ assertNotRegistered(MOCK_REPOSITORY_NAME_ALT);
+ factory.registerRepository(MOCK_REPOSITORY_NAME_ALT, mockRepository);
+ assertRegistered(MOCK_REPOSITORY_NAME_ALT, mockRepository);
+ }
+
+ @Test
+ public void shouldUnregisterRepositoryInJndiIfNullRepositoryReference() throws
Exception {
+ assertRegistered(MOCK_REPOSITORY_NAME, mockRepository);
+ factory.registerRepository(MOCK_REPOSITORY_NAME, null);
+ assertNotRegistered(MOCK_REPOSITORY_NAME);
+ }
+
+ @Test
+ public void shouldRemoveAllTrailingDelimitersWhenRegisteringRepository() throws
Exception {
+ assertNotRegistered("java:jcr/unit/test/repository");
+ factory.registerRepository("java:jcr/unit/test/repository///",
mockRepository);
+ assertRegistered("java:jcr/unit/test/repository", mockRepository);
+ }
+
+ @Test
+ public void shouldCreateAnonymousSessionInRepositoryIfNoCredentialsAreRegisterd()
throws Exception {
+ Repository repository = getRepository();
+ factory.registerRepository("java:jcr/unit/test/repository/",
repository);
+ session =
factory.createSession("java:jcr/unit/test/repository/default");
+ assertThat(session, is(notNullValue()));
+ assertThat(session.getUserID(), is("anonymous")); // as defined in the
Jackrabbit configuration file
+ }
+
+ @Test
+ public void shouldCreateNonAnonymousSessionInRepositoryIfCredentialsAreRegistered()
throws Exception {
+ Repository repository = getRepository();
+ factory.registerRepository("java:jcr/unit/test/repository/",
repository);
+ factory.registerCredentials("java:jcr/unit/test/repository/default",
"jsmith", "secret".toCharArray());
+ session =
factory.createSession("java:jcr/unit/test/repository/default");
+ assertThat(session, is(notNullValue()));
+ assertThat(session.getUserID(), is("jsmith")); // as defined in the
Jackrabbit configuration file
+ }
+}
Property changes on:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/JndiSessionFactoryTest.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Added:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/SimpleExecutionContext.java
===================================================================
---
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/SimpleExecutionContext.java
(rev 0)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/SimpleExecutionContext.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,67 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.repository.util;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import java.io.IOException;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import org.jboss.dna.common.SystemFailureException;
+import org.jboss.dna.common.jcr.AbstractJcrRepositoryTest;
+import org.jboss.dna.repository.util.ExecutionContext;
+import org.jboss.dna.repository.util.JcrTools;
+import org.jboss.dna.repository.util.SessionFactory;
+
+/**
+ * @author Randall Hauch
+ */
+public class SimpleExecutionContext implements ExecutionContext {
+
+ private JcrTools tools = new JcrTools();
+ private SessionFactory sessionFactory;
+
+ public SimpleExecutionContext( final AbstractJcrRepositoryTest test, final String
repositoryName ) {
+ this.sessionFactory = new SessionFactory() {
+
+ public Session createSession( String name ) throws RepositoryException {
+ assertThat(name, is(repositoryName));
+ try {
+ return test.getRepository().login(test.getTestCredentials());
+ } catch (IOException e) {
+ throw new SystemFailureException(e);
+ }
+ }
+ };
+ }
+
+ public SessionFactory getSessionFactory() {
+ return sessionFactory;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JcrTools getTools() {
+ return tools;
+ }
+}
Property changes on:
trunk/dna-repository/src/test/java/org/jboss/dna/repository/util/SimpleExecutionContext.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Deleted:
trunk/dna-repository/src/test/java/org/jboss/dna/services/JndiSessionFactoryTest.java
===================================================================
---
trunk/dna-services/src/test/java/org/jboss/dna/services/JndiSessionFactoryTest.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/services/JndiSessionFactoryTest.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,167 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNull.notNullValue;
-import static org.hamcrest.core.IsNull.nullValue;
-import static org.hamcrest.core.IsSame.sameInstance;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.fail;
-import javax.jcr.Repository;
-import javax.jcr.Session;
-import javax.naming.InitialContext;
-import javax.naming.NamingException;
-import org.jboss.dna.common.SystemFailureException;
-import org.jboss.dna.common.jcr.AbstractJcrRepositoryTest;
-import org.jboss.dna.common.naming.MockInitialContext;
-import org.jmock.Mockery;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * @author Randall Hauch
- */
-public class JndiSessionFactoryTest extends AbstractJcrRepositoryTest {
-
- public static final String MOCK_REPOSITORY_NAME =
"java:jcr/unit/test/repository/1";
- public static final String MOCK_REPOSITORY_NAME_ALT =
"java:jcr/unit/test/repository/2";
-
- private JndiSessionFactory factory;
- private Mockery context;
- private Repository mockRepository;
- private Session session;
-
- @Before
- public void beforeEach() throws Exception {
- this.context = new Mockery();
- this.mockRepository = this.context.mock(Repository.class);
- MockInitialContext.register(MOCK_REPOSITORY_NAME, this.mockRepository);
- this.factory = new JndiSessionFactory();
- }
-
- @After
- public void afterEach() throws Exception {
- if (session != null) {
- session.logout();
- }
- MockInitialContext.tearDown();
- }
-
- protected void assertNotRegistered( String name ) {
- try {
- new InitialContext().lookup(name);
- fail("Unexpectedly found registered object");
- } catch (NamingException e) {
- // expected ...
- }
- }
-
- protected void assertRegistered( String name, Object obj ) {
- try {
- assertThat(new InitialContext().lookup(name), is(sameInstance(obj)));
- } catch (NamingException e) {
- fail("Failed to find registered object \"" + name +
"\"");
- }
- }
-
- @Test
- public void shouldCreateSessionForRegisteredRepository() {
-
- }
-
- @Test( expected = SystemFailureException.class )
- public void shouldThrowSystemFailureWhenUnableToFindRegisteredRepository() throws
Exception {
- factory.createSession(MOCK_REPOSITORY_NAME +
"something_extra_that_can't_be_found");
- }
-
- @Test
- public void shouldFindWorkspaceInRegisteredName() {
-
assertThat(factory.getWorkspaceName("java:jcr/path/to/repository/workspaceName"),
is("workspaceName"));
- }
-
- @Test
- public void shouldReturnNullWorkspaceIfRegisteredNameEndsInDelimiter() {
- assertThat(factory.getWorkspaceName("java:jcr/path/to/repository/"),
is(nullValue()));
- }
-
- @Test
- public void shouldReturnNullWorkspaceIfRegisteredNameHasNoDelimiter() {
- assertThat(factory.getWorkspaceName("java:jcr"), is(nullValue()));
- }
-
- @Test
- public void shouldFindRepositoryInRegisteredName() {
-
assertThat(factory.getRepositoryName("java:jcr/path/to/repository/workspaceName"),
is("java:jcr/path/to/repository"));
- }
-
- @Test
- public void
shouldConsiderWholeRegisteredNameToBeRepositoryNameIfRegisteredNameEndsInDelimiter() {
- assertThat(factory.getRepositoryName("java:jcr/path/to/repository/"),
is("java:jcr/path/to/repository"));
- }
-
- @Test
- public void
shouldConsiderWholeRegisteredNameToBeRepositoryNameIfRegisteredNameHasNoDelimiter() {
- assertThat(factory.getRepositoryName("java:jcr"),
is("java:jcr"));
- }
-
- @Test
- public void shouldRegisterSuppliedRepositoryInJndi() throws Exception {
- assertNotRegistered(MOCK_REPOSITORY_NAME_ALT);
- factory.registerRepository(MOCK_REPOSITORY_NAME_ALT, mockRepository);
- assertRegistered(MOCK_REPOSITORY_NAME_ALT, mockRepository);
- }
-
- @Test
- public void shouldUnregisterRepositoryInJndiIfNullRepositoryReference() throws
Exception {
- assertRegistered(MOCK_REPOSITORY_NAME, mockRepository);
- factory.registerRepository(MOCK_REPOSITORY_NAME, null);
- assertNotRegistered(MOCK_REPOSITORY_NAME);
- }
-
- @Test
- public void shouldRemoveAllTrailingDelimitersWhenRegisteringRepository() throws
Exception {
- assertNotRegistered("java:jcr/unit/test/repository");
- factory.registerRepository("java:jcr/unit/test/repository///",
mockRepository);
- assertRegistered("java:jcr/unit/test/repository", mockRepository);
- }
-
- @Test
- public void shouldCreateAnonymousSessionInRepositoryIfNoCredentialsAreRegisterd()
throws Exception {
- Repository repository = getRepository();
- factory.registerRepository("java:jcr/unit/test/repository/",
repository);
- session =
factory.createSession("java:jcr/unit/test/repository/default");
- assertThat(session, is(notNullValue()));
- assertThat(session.getUserID(), is("anonymous")); // as defined in the
Jackrabbit configuration file
- }
-
- @Test
- public void shouldCreateNonAnonymousSessionInRepositoryIfCredentialsAreRegistered()
throws Exception {
- Repository repository = getRepository();
- factory.registerRepository("java:jcr/unit/test/repository/",
repository);
- factory.registerCredentials("java:jcr/unit/test/repository/default",
"jsmith", "secret".toCharArray());
- session =
factory.createSession("java:jcr/unit/test/repository/default");
- assertThat(session, is(notNullValue()));
- assertThat(session.getUserID(), is("jsmith")); // as defined in the
Jackrabbit configuration file
- }
-}
Deleted:
trunk/dna-repository/src/test/java/org/jboss/dna/services/SimpleExecutionContext.java
===================================================================
---
trunk/dna-services/src/test/java/org/jboss/dna/services/SimpleExecutionContext.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/dna-repository/src/test/java/org/jboss/dna/services/SimpleExecutionContext.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -1,65 +0,0 @@
-/*
- * JBoss, Home of Professional Open Source.
- * Copyright 2008, Red Hat Middleware LLC, and individual contributors
- * as indicated by the @author tags. See the copyright.txt file in the
- * distribution for a full listing of individual contributors.
- *
- * This is free software; you can redistribute it and/or modify it
- * under the terms of the GNU Lesser General Public License as
- * published by the Free Software Foundation; either version 2.1 of
- * the License, or (at your option) any later version.
- *
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this software; if not, write to the Free
- * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
- * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
- */
-package org.jboss.dna.services;
-
-import static org.hamcrest.core.Is.is;
-import static org.junit.Assert.assertThat;
-import java.io.IOException;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import org.jboss.dna.common.SystemFailureException;
-import org.jboss.dna.common.jcr.AbstractJcrRepositoryTest;
-import org.jboss.dna.services.util.JcrTools;
-
-/**
- * @author Randall Hauch
- */
-public class SimpleExecutionContext implements ExecutionContext {
-
- private JcrTools tools = new JcrTools();
- private SessionFactory sessionFactory;
-
- public SimpleExecutionContext( final AbstractJcrRepositoryTest test, final String
repositoryName ) {
- this.sessionFactory = new SessionFactory() {
-
- public Session createSession( String name ) throws RepositoryException {
- assertThat(name, is(repositoryName));
- try {
- return test.getRepository().login(test.getTestCredentials());
- } catch (IOException e) {
- throw new SystemFailureException(e);
- }
- }
- };
- }
-
- public SessionFactory getSessionFactory() {
- return sessionFactory;
- }
-
- /**
- * {@inheritDoc}
- */
- public JcrTools getTools() {
- return tools;
- }
-}
Modified: trunk/dna-repository/src/test/resources/log4j.properties
===================================================================
--- trunk/dna-services/src/test/resources/log4j.properties 2008-04-22 01:42:33 UTC (rev
95)
+++ trunk/dna-repository/src/test/resources/log4j.properties 2008-04-22 02:47:53 UTC (rev
96)
@@ -9,7 +9,7 @@
# Set up the default logging to be INFO level, then override specific units
log4j.logger.org.jboss.dna=INFO
-log4j.logger.org.jboss.dna.services.sequencers=TRACE
+log4j.logger.org.jboss.dna.repository.sequencers=TRACE
# Jackrabbit logging
log4j.logger.org.apache.jackrabbit=WARN, stdout
Modified: trunk/dna-repository/src/test/resources/rule_test.dslr
===================================================================
--- trunk/dna-services/src/test/resources/rule_test.dslr 2008-04-22 01:42:33 UTC (rev 95)
+++ trunk/dna-repository/src/test/resources/rule_test.dslr 2008-04-22 02:47:53 UTC (rev
96)
@@ -1,5 +1,5 @@
#created on: Jan 4, 2008
-package org.jboss.dna.services.rules
+package org.jboss.dna.repository.rules
#list any import classes here.
Property changes on: trunk/dna-spi
___________________________________________________________________
Name: svn:ignore
+ target
Added: trunk/dna-spi/.classpath
===================================================================
--- trunk/dna-spi/.classpath (rev 0)
+++ trunk/dna-spi/.classpath 2008-04-22 02:47:53 UTC (rev 96)
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path="src/main/java"/>
+ <classpathentry kind="src" path="src/main/resources"/>
+ <classpathentry kind="src" output="target/test-classes"
path="src/test/java"/>
+ <classpathentry kind="src" output="target/test-classes"
path="src/test/resources"/>
+ <classpathentry kind="con"
path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="con"
path="org.maven.ide.eclipse.MAVEN2_CLASSPATH_CONTAINER"/>
+ <classpathentry kind="output" path="target/classes"/>
+</classpath>
Added: trunk/dna-spi/.project
===================================================================
--- trunk/dna-spi/.project (rev 0)
+++ trunk/dna-spi/.project 2008-04-22 02:47:53 UTC (rev 96)
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>dna-spi</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.maven.ide.eclipse.maven2Builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.maven.ide.eclipse.maven2Nature</nature>
+ </natures>
+</projectDescription>
Added: trunk/dna-spi/pom.xml
===================================================================
--- trunk/dna-spi/pom.xml (rev 0)
+++ trunk/dna-spi/pom.xml 2008-04-22 02:47:53 UTC (rev 96)
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?><project>
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </parent>
+ <!-- The groupId and version values are inherited from parent -->
+ <artifactId>dna-spi</artifactId>
+ <description>The JBoss DNA Service Provider Interface (SPI) used to create
extensions</description>
+ <packaging>jar</packaging>
+ <name>JBoss DNA Service Provider Interface (SPI)</name>
+ <
url>http://labs.jboss.org/dna</url>
+ <!--
+ Define the dependencies. Note that all version and scopes default to those
+ defined in the dependencyManagement section of the parent pom.
+ -->
+ <dependencies>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-common</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-common</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ Testing (note the scope)
+ -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.jmock</groupId>
+ <artifactId>jmock-junit4</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ Logging (require SLF4J API for compiling, but use Log4J and its SLF4J binding for
testing)
+ -->
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-log4j12</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <!--
+ Java Concurrency in Practice annotations
+ -->
+ <dependency>
+ <groupId>net.jcip</groupId>
+ <artifactId>jcip-annotations</artifactId>
+ </dependency>
+ </dependencies>
+</project>
Added: trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/SequencerOutput.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/SequencerOutput.java
(rev 0)
+++
trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/SequencerOutput.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,68 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.spi.sequencers;
+
+import java.io.InputStream;
+import java.util.Calendar;
+
+/**
+ * Interface for sequencers to use to generate their output.
+ *
+ * @author Randall Hauch
+ */
+public interface SequencerOutput {
+
+ /**
+ * Set the supplied property on the supplied node.
+ * <p>
+ * The allowable values are any of the following:
+ * <ul>
+ * <li>primitives (which will be autoboxed)</li>
+ * <li>{@link String} instances</li>
+ * <li>{@link String} arrays</li>
+ * <li>byte arrays</li>
+ * <li>{@link InputStream} instances</li>
+ * <li>{@link Calendar} instances</li>
+ * <li></li>
+ * </ul>
+ * </p>
+ *
+ * @param nodePath the path to the node containing the property; may not be null
+ * @param property the name of the property to be set
+ * @param values the value(s) for the property; may be empty if any existing property is
to be removed
+ */
+ void setProperty( String nodePath,
+ String property,
+ Object... values );
+
+ /**
+ * Set the supplied reference on the supplied node.
+ *
+ * @param nodePath the path to the node containing the property; may not be null
+ * @param property the name of the property to be set
+ * @param paths the paths to the referenced property, which may be absolute paths or
relative to the sequencer output node;
+ * may be empty if any existing property is to be removed
+ */
+ void setReference( String nodePath,
+ String property,
+ String... paths );
+}
Added: trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/StreamSequencer.java
===================================================================
--- trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/StreamSequencer.java
(rev 0)
+++
trunk/dna-spi/src/main/java/org/jboss/dna/spi/sequencers/StreamSequencer.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -0,0 +1,68 @@
+/*
+ * JBoss, Home of Professional Open Source.
+ * Copyright 2008, Red Hat Middleware LLC, and individual contributors
+ * as indicated by the @author tags. See the copyright.txt file in the
+ * distribution for a full listing of individual contributors.
+ *
+ * This is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This software is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.spi.sequencers;
+
+import java.io.InputStream;
+import org.jboss.dna.common.monitor.ProgressMonitor;
+
+/**
+ * The interface for a DNA sequencer that processes a property as a stream to extract
information from the content and store in
+ * the repository.
+ * <p>
+ * Implementations must provide a no-argument constructor.
+ * </p>
+ * @author Randall Hauch
+ */
+public interface StreamSequencer {
+
+ /**
+ * Sequence the data found in the supplied stream, placing the output information
into the supplied map.
+ * <p>
+ * JBoss DNA's SequencingService determines the sequencers that should be
executed by monitoring the changes to one or more
+ * workspaces that it is monitoring. Changes in those workspaces are aggregated and
used to determine which sequencers should
+ * be called. If the sequencer implements this interface, then this method is called
with the property that is to be sequenced
+ * along with the interface used to register the output. The framework takes care of
all the rest.
+ * </p>
+ * <p>
+ * This operation should report progress to the supplied {@link ProgressMonitor}. At
the beginning of the operation, call
+ * {@link ProgressMonitor#beginTask(double, org.jboss.dna.common.i18n.I18n,
Object...)} with a meaningful message describing
+ * the operation and a total for the amount of work that will be done by this
sequencer. Then perform the sequencing work,
+ * periodically reporting work by specifying the {@link
ProgressMonitor#worked(double) amount of work} that has was just
+ * completed or by {@link ProgressMonitor#createSubtask(double) creating a subtask}
and reporting work against that subtask
+ * monitor.
+ * </p>
+ * <p>
+ * The implementation should also periodically check whether the operation has been
+ * {@link ProgressMonitor#isCancelled() cancelled}. If this method returns true, the
implementation should abort all work as
+ * soon as possible and close any resources that were acquired or opened.
+ * </p>
+ * <p>
+ * Finally, the implementation should call {@link ProgressMonitor#done()} when the
operation has finished.
+ * </p>
+ * @param stream the stream with the data to be sequenced; never null
+ * @param output the output from the sequencing operation; never null
+ * @param progressMonitor the progress monitor that should be kept updated with the
sequencer's progress and that should be
+ * frequently consulted as to whether this operation has been {@link
ProgressMonitor#isCancelled() cancelled}.
+ */
+ void sequence( InputStream stream, SequencerOutput output, ProgressMonitor
progressMonitor );
+
+}
Added: trunk/dna-spi/src/test/resources/log4j.properties
===================================================================
--- trunk/dna-spi/src/test/resources/log4j.properties (rev 0)
+++ trunk/dna-spi/src/test/resources/log4j.properties 2008-04-22 02:47:53 UTC (rev 96)
@@ -0,0 +1,16 @@
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %m%n
+
+# Root logger option
+log4j.rootLogger=INFO, stdout
+
+# Set up the default logging to be INFO level, then override specific units
+log4j.logger.org.jboss.dna=INFO
+
+# Jackrabbit logging
+log4j.logger.org.apache.jackrabbit=WARN, stdout
+log4j.logger.org.apache.derby=INFO, stdout
+
Modified: trunk/pom.xml
===================================================================
--- trunk/pom.xml 2008-04-22 01:42:33 UTC (rev 95)
+++ trunk/pom.xml 2008-04-22 02:47:53 UTC (rev 96)
@@ -11,7 +11,7 @@
<modules>
<module>dna-common</module>
<module>dna-maven-classloader</module>
- <module>dna-services</module>
+ <module>dna-repository</module>
<module>sequencers/dna-sequencer-images</module>
<module>dna-integration-tests</module>
</modules>
@@ -98,9 +98,14 @@
</dependency>
<dependency>
<groupId>org.jboss.dna</groupId>
- <artifactId>dna-services</artifactId>
+ <artifactId>dna-spi</artifactId>
<version>0.1-SNAPSHOT</version>
</dependency>
+ <dependency>
+ <groupId>org.jboss.dna</groupId>
+ <artifactId>dna-repository</artifactId>
+ <version>0.1-SNAPSHOT</version>
+ </dependency>
<!--
Rules
-->
Modified: trunk/sequencers/dna-sequencer-images/pom.xml
===================================================================
--- trunk/sequencers/dna-sequencer-images/pom.xml 2008-04-22 01:42:33 UTC (rev 95)
+++ trunk/sequencers/dna-sequencer-images/pom.xml 2008-04-22 02:47:53 UTC (rev 96)
@@ -33,7 +33,7 @@
</dependency>
<dependency>
<groupId>org.jboss.dna</groupId>
- <artifactId>dna-services</artifactId>
+ <artifactId>dna-spi</artifactId>
</dependency>
<!--
Testing (note the scope)
Modified:
trunk/sequencers/dna-sequencer-images/src/main/java/org/jboss/dna/sequencer/images/ImageMetadataSequencer.java
===================================================================
---
trunk/sequencers/dna-sequencer-images/src/main/java/org/jboss/dna/sequencer/images/ImageMetadataSequencer.java 2008-04-22
01:42:33 UTC (rev 95)
+++
trunk/sequencers/dna-sequencer-images/src/main/java/org/jboss/dna/sequencer/images/ImageMetadataSequencer.java 2008-04-22
02:47:53 UTC (rev 96)
@@ -23,9 +23,8 @@
import java.io.InputStream;
import org.jboss.dna.common.monitor.ProgressMonitor;
-import org.jboss.dna.services.ExecutionContext;
-import org.jboss.dna.services.sequencers.SequencerOutput;
-import org.jboss.dna.services.sequencers.StreamSequencer;
+import org.jboss.dna.spi.sequencers.SequencerOutput;
+import org.jboss.dna.spi.sequencers.StreamSequencer;
/**
* A sequencer that processes the binary content of an image file, extracts the metadata
for the image, and then writes that image
@@ -62,7 +61,7 @@
* </p>
* @author Randall Hauch
*/
-public class ImageMetadataSequencer extends StreamSequencer {
+public class ImageMetadataSequencer implements StreamSequencer {
public static final String METADATA_NODE = "image:metadata";
public static final String IMAGE_PRIMARY_TYPE = "jcr:primaryType";
@@ -83,8 +82,7 @@
/**
* {@inheritDoc}
*/
- @Override
- protected void sequence( InputStream stream, SequencerOutput output, ExecutionContext
context, ProgressMonitor progressMonitor ) {
+ public void sequence( InputStream stream, SequencerOutput output, ProgressMonitor
progressMonitor ) {
ImageMetadata metadata = new ImageMetadata();
metadata.setInput(stream);
metadata.setDetermineImageNumber(true);