New project conventions
authorMathieu Baudier <mbaudier@argeo.org>
Tue, 25 Nov 2014 12:47:53 +0000 (12:47 +0000)
committerMathieu Baudier <mbaudier@argeo.org>
Tue, 25 Nov 2014 12:47:53 +0000 (12:47 +0000)
git-svn-id: https://svn.argeo.org/commons/trunk@7522 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc

90 files changed:
org.argeo.server.jcr/.classpath
org.argeo.server.jcr/bnd.bnd [new file with mode: 0644]
org.argeo.server.jcr/ext/test/log4j.properties [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/jcr/CollectionsObject.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/jcr/OtherObject.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/jcr/SimpleObject.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/jcr/tabular/JcrTabularTest.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/JcrResourceAdapterTest.java [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy00.xls [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy01.xls [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy02.xls [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy03.xls [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/repository-h2.xml [new file with mode: 0644]
org.argeo.server.jcr/ext/test/org/argeo/server/jcr/repository-memory.xml [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrConstants.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrUtils.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoNames.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoTypes.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/CollectionNodeIterator.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultJcrListener.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryFactory.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryRegister.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrCallback.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrRepositoryWrapper.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrResourceAdapter.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUrlStreamHandler.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUtils.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/MaintainedRepository.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapper.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapperProvider.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/PropertyDiff.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/RepositoryRegister.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/UserJcrUtils.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/VersionDiff.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/argeo.cnd [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/AbstractUrlProxy.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/ResourceProxy.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/security/JcrAuthorizations.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/BeanNodeMapper.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/ThreadBoundSession.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularRowIterator.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularWriter.java [new file with mode: 0644]
org.argeo.server.jcr/newsrc/org/argeo/jcr/unit/AbstractJcrTestCase.java [new file with mode: 0644]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoTypes.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/CollectionNodeIterator.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultJcrListener.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryRegister.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrCallback.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrResourceAdapter.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUrlStreamHandler.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/MaintainedRepository.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/NodeMapper.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/NodeMapperProvider.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/PropertyDiff.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/RepositoryRegister.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/VersionDiff.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/proxy/AbstractUrlProxy.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/proxy/ResourceProxy.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/spring/BeanNodeMapper.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/spring/ThreadBoundSession.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/tabular/JcrTabularRowIterator.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/tabular/JcrTabularWriter.java [deleted file]
org.argeo.server.jcr/src/main/java/org/argeo/jcr/unit/AbstractJcrTestCase.java [deleted file]
org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/jcr/CollectionsObject.java [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/jcr/OtherObject.java [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/jcr/SimpleObject.java [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java [deleted file]
org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java [deleted file]
org.argeo.server.jcr/src/test/resources/log4j.properties [deleted file]
org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy00.xls [deleted file]
org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy01.xls [deleted file]
org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy02.xls [deleted file]
org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy03.xls [deleted file]
org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-h2.xml [deleted file]
org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml [deleted file]

index d40e15cbf748a365d7ef303a34eceb3fce215f2e..cbc801ced744e08298be0f86c86d6d15e9c75d54 100644 (file)
@@ -1,10 +1,10 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <classpath>
-       <classpathentry kind="src" output="target/classes" path="src/main/java"/>
-       <classpathentry kind="src" output="target/classes" 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/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>>>
-       <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
-       <classpathentry kind="output" path="target/classes"/>
+       <classpathentry kind="src" path="newsrc" />
+       <classpathentry kind="src" path="ext/test" />
+       <classpathentry kind="con"
+               path="org.eclipse.pde.core.requiredPlugins" />
+       <classpathentry kind="con"
+               path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
+       <classpathentry kind="output" path="bin" />
 </classpath>
diff --git a/org.argeo.server.jcr/bnd.bnd b/org.argeo.server.jcr/bnd.bnd
new file mode 100644 (file)
index 0000000..5907070
--- /dev/null
@@ -0,0 +1,197 @@
+# BND instructions
+#-----------------------------------------------------------------------
+#Tue Nov 25 13:14:59 CET 2014
+pom.id=org.argeo.commons\:org.argeo.server.jcr\:jar\:2.1.12-SNAPSHOT
+env.DESKTOP_SESSION=gnome
+env.OLDPWD=/home/mbaudier/dev/src/commons
+file.encoding.pkg=sun.io
+java.home=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre
+env.GDM_LANG=en_GB.utf8
+env.DISPLAY=\:0.0
+env.LS_COLORS=rs\=0\:di\=01;34\:ln\=01;36\:mh\=00\:pi\=40;33\:so\=01;35\:do\=01;35\:bd\=40;33;01\:cd\=40;33;01\:or\=40;31;01\:mi\=01;05;37;41\:su\=37;41\:sg\=30;43\:ca\=30;41\:tw\=30;42\:ow\=34;42\:st\=37;44\:ex\=01;32\:*.tar\=01;31\:*.tgz\=01;31\:*.arj\=01;31\:*.taz\=01;31\:*.lzh\=01;31\:*.lzma\=01;31\:*.tlz\=01;31\:*.txz\=01;31\:*.zip\=01;31\:*.z\=01;31\:*.Z\=01;31\:*.dz\=01;31\:*.gz\=01;31\:*.lz\=01;31\:*.xz\=01;31\:*.bz2\=01;31\:*.tbz\=01;31\:*.tbz2\=01;31\:*.bz\=01;31\:*.tz\=01;31\:*.deb\=01;31\:*.rpm\=01;31\:*.jar\=01;31\:*.rar\=01;31\:*.ace\=01;31\:*.zoo\=01;31\:*.cpio\=01;31\:*.7z\=01;31\:*.rz\=01;31\:*.jpg\=01;35\:*.jpeg\=01;35\:*.gif\=01;35\:*.bmp\=01;35\:*.pbm\=01;35\:*.pgm\=01;35\:*.ppm\=01;35\:*.tga\=01;35\:*.xbm\=01;35\:*.xpm\=01;35\:*.tif\=01;35\:*.tiff\=01;35\:*.png\=01;35\:*.svg\=01;35\:*.svgz\=01;35\:*.mng\=01;35\:*.pcx\=01;35\:*.mov\=01;35\:*.mpg\=01;35\:*.mpeg\=01;35\:*.m2v\=01;35\:*.mkv\=01;35\:*.ogm\=01;35\:*.mp4\=01;35\:*.m4v\=01;35\:*.mp4v\=01;35\:*.vob\=01;35\:*.qt\=01;35\:*.nuv\=01;35\:*.wmv\=01;35\:*.asf\=01;35\:*.rm\=01;35\:*.rmvb\=01;35\:*.flc\=01;35\:*.avi\=01;35\:*.fli\=01;35\:*.flv\=01;35\:*.gl\=01;35\:*.dl\=01;35\:*.xcf\=01;35\:*.xwd\=01;35\:*.yuv\=01;35\:*.cgm\=01;35\:*.emf\=01;35\:*.axv\=01;35\:*.anx\=01;35\:*.ogv\=01;35\:*.ogx\=01;35\:*.aac\=01;36\:*.au\=01;36\:*.flac\=01;36\:*.mid\=01;36\:*.midi\=01;36\:*.mka\=01;36\:*.mp3\=01;36\:*.mpc\=01;36\:*.ogg\=01;36\:*.ra\=01;36\:*.wav\=01;36\:*.axa\=01;36\:*.oga\=01;36\:*.spx\=01;36\:*.xspf\=01;36\:
+pom.contributors=
+project.build.developers=
+classworlds.conf=/opt/apache-maven/bin/m2.conf
+env.XDG_SESSION_COOKIE=9bf883dac7be1df0f982e1dd0000003a-1416578122.683229-877522490
+pom.pomFile=/home/mbaudier/dev/src/commons/org.argeo.server.jcr/pom.xml
+java.endorsed.dirs=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/endorsed
+project.build.groupId=org.argeo.commons
+env.LOGNAME=mbaudier
+env.USERNAME=mbaudier
+project.build.scm=org.apache.maven.model.Scm@14f65802
+env.SESSION_MANAGER=local/unix\:@/tmp/.ICE-unix/17546,unix/unix\:/tmp/.ICE-unix/17546
+env.KDEDIRS=/usr
+sun.os.patch.level=unknown
+java.vendor.url=http\://java.oracle.com/
+pom.profiles=
+env.G_BROKEN_FILENAMES=1
+maven-symbolicname=org.argeo.commons.org.argeo.server.jcr
+java.version=1.7.0_71
+env.ORBIT_SOCKETDIR=/tmp/orbit-mbaudier
+project.build.contributors=
+version.argeo-commons=2.1.12-SNAPSHOT
+java.vendor.url.bug=http\://bugreport.sun.com/bugreport/
+user.name=mbaudier
+env.LANG=en_GB.utf8
+env.CVS_RSH=ssh
+project.build.pomFile=/home/mbaudier/dev/src/commons/org.argeo.server.jcr/pom.xml
+sun.io.unicode.encoding=UnicodeLittle
+sun.jnu.encoding=UTF-8
+env.DBUS_SESSION_BUS_ADDRESS=unix\:abstract\=/tmp/dbus-OazDk3vN1t,guid\=230af52c9acfa834ca05b3b0000059f6
+java.runtime.name=OpenJDK Runtime Environment
+project.build.outputdirectory=/home/mbaudier/dev/src/commons/org.argeo.server.jcr/target/classes
+env.SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass
+rpm.release=6
+project.version=2.1.12-SNAPSHOT
+java.specification.name=Java Platform API Specification
+user.timezone=
+env.LESSOPEN=||/usr/bin/lesspipe.sh %s
+pom.mailingLists=
+path.separator=\:
+project.contributors=
+project.inceptionYear=2007
+pom.artifactId=org.argeo.server.jcr
+env.MAVEN_CMD_LINE_ARGS=-o clean install
+project.name=Commons Server JCR
+env.XMODIFIERS=@im\=none
+file.encoding=UTF-8
+env.HOME=/home/mbaudier
+sun.java.command=org.codehaus.plexus.classworlds.launcher.Launcher -o clean install
+env.GTK_RC_FILES=/etc/gtk/gtkrc\:/home/mbaudier/.gtkrc-1.2-gnome2
+project.id=org.argeo.commons\:org.argeo.server.jcr\:jar\:2.1.12-SNAPSHOT
+env.HOSTNAME=mostar
+Export-Package=org.argeo.jcr.*
+Bundle-License=http\://www.apache.org/licenses/LICENSE-2.0.txt
+java.io.tmpdir=/tmp
+project.build.mailingLists=
+env.GNOME_KEYRING_PID=17536
+user.language=en
+Include-Resource=org/argeo/jcr/argeo.cnd\=src/main/resources/org/argeo/jcr/argeo.cnd
+env.HISTCONTROL=ignoredups
+line.separator=\n
+project.build.directory=/home/mbaudier/dev/src/commons/org.argeo.server.jcr/target
+env.HISTSIZE=1000
+java.vm.info=mixed mode
+developmentCycle.argeo-commons.startDate=2012-12-19
+sun.desktop=gnome
+java.vm.specification.name=Java Virtual Machine Specification
+project.modelEncoding=UTF-8
+env.COLORTERM=gnome-terminal
+project.build.id=org.argeo.commons\:org.argeo.server.jcr\:jar\:2.1.12-SNAPSHOT
+project.build.licenses=org.apache.maven.model.License@390bf60e
+version.rap.addons=0.5.0.argeo.20141029
+pom.modelVersion=4.0.0
+project.licenses=org.apache.maven.model.License@390bf60e
+env.GDMSESSION=gnome
+project.developers=
+java.awt.printerjob=sun.print.PSPrinterJob
+-removeheaders=Bnd-LastModified,Build-Jdk,Built-By,Tool,Created-By
+pom.version=2.1.12-SNAPSHOT
+env.WINDOWID=73400323
+pom.scm=org.apache.maven.model.Scm@14f65802
+-plugin=org.apache.felix.bundleplugin.BlueprintPlugin,aQute.lib.spring.SpringXMLType
+project.mailingLists=
+pom.packaging=jar
+project.build.parent=org.argeo.commons\:argeo-commons\:pom\:2.1.12-SNAPSHOT
+project.projectDirectory=/home/mbaudier/dev/src/commons/org.argeo.server.jcr
+os.name=Linux
+project.build.modelVersion=4.0.0
+java.specification.vendor=Oracle Corporation
+env.TERM=xterm
+java.vm.name=OpenJDK 64-Bit Server VM
+env.QT_IM_MODULE=xim
+java.library.path=/usr/java/packages/lib/amd64\:/usr/lib64\:/lib64\:/lib\:/usr/lib
+project.build.build=org.apache.maven.model.Build@40472e64
+env.PATH=/usr/lib64/qt-3.3/bin\:/usr/local/bin\:/usr/bin\:/bin\:/usr/local/sbin\:/usr/sbin\:/sbin\:/home/mbaudier/bin
+pom.build=org.apache.maven.model.Build@40472e64
+java.class.version=51.0
+env.SHLVL=2
+project.build.version=2.1.12-SNAPSHOT
+env.GNOME_KEYRING_SOCKET=/tmp/keyring-YLbPHE/socket
+pom.parent=org.argeo.commons\:argeo-commons\:pom\:2.1.12-SNAPSHOT
+pom.projectDirectory=/home/mbaudier/dev/src/commons/org.argeo.server.jcr
+project.modelVersion=4.0.0
+rpm.stagingRepository=/srv/rpmfactory/argeo-osgi-2-staging/6/x86_64
+Bundle-SymbolicName=org.argeo.server.jcr
+sun.boot.library.path=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/amd64
+project.build.sourceEncoding=UTF-8
+Private-Package=org.argeo.jcr;org.argeo.jcr.proxy;org.argeo.jcr.security;org.argeo.jcr.spring;org.argeo.jcr.tabular;org.argeo.jcr.unit;-split-package\:\=merge-first
+project.build.artifactId=org.argeo.server.jcr
+sun.management.compiler=HotSpot 64-Bit Tiered Compilers
+java.awt.graphicsenv=sun.awt.X11GraphicsEnvironment
+pom.name=Commons Server JCR
+env.USER=mbaudier
+pom.developers=
+project.build=org.apache.maven.model.Build@40472e64
+project.scm=org.apache.maven.model.Scm@14f65802
+project.baseDir=/home/mbaudier/dev/src/commons/org.argeo.server.jcr
+project.build.name=Commons Server JCR
+java.vm.specification.version=1.7
+env.KDE_IS_PRELINKED=1
+project.build.inceptionYear=2007
+env.GDM_KEYBOARD_LAYOUT=de
+project.groupId=org.argeo.commons
+Bundle-Version=2.1.12-SNAPSHOT-r${tstamp}
+env.NLSPATH=/usr/dt/lib/nls/msg/%L/%N.cat
+awt.toolkit=sun.awt.X11.XToolkit
+project.build.profiles=
+sun.cpu.isalist=
+developmentCycle.argeo-commons=2.1
+project.profiles=
+java.ext.dirs=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/ext\:/usr/java/packages/lib/ext
+Import-Package=junit.framework;resolution\:\=optional,org.xml.sax;version\="0.0.0",org.springframework.core;resolution\:\=optional,org.springframework.core.io;resolution\:\=optional,org.springframework.*;resolution\:\=optional,*
+os.version=2.6.32-504.1.3.el6.x86_64
+user.home=/home/mbaudier
+java.vm.vendor=Oracle Corporation
+env.JAVA_HOME=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64
+SLC-Category=org.argeo.commons
+env.XAUTHORITY=/var/run/gdm/auth-for-mbaudier-7t5RHJ/database
+user.dir=/home/mbaudier/dev/src/commons
+env.XFILESEARCHPATH=/usr/dt/app-defaults/%L/Dt
+env.WINDOWPATH=1
+env.MAIL=/var/spool/mail/mbaudier
+project.build.modelEncoding=UTF-8
+env.PWD=/home/mbaudier/dev/src/commons
+project.parent=org.argeo.commons\:argeo-commons\:pom\:2.1.12-SNAPSHOT
+sun.cpu.endian=little
+env.QTLIB=/usr/lib64/qt-3.3/lib
+pom.licenses=org.apache.maven.model.License@390bf60e
+project.dir=/home/mbaudier/dev/src/commons/org.argeo.server.jcr
+classifier=
+version.argeo-distribution=1.4.0
+java.vm.version=24.65-b04
+java.class.path=/opt/apache-maven/boot/plexus-classworlds-2.5.1.jar
+env.QTDIR=/usr/lib64/qt-3.3
+os.arch=amd64
+maven.build.version=Apache Maven 3.2.3 (33f8c3e1027c3ddde99d3cdebad2656a31e8fdf4; 2014-08-11T22\:58\:10+02\:00)
+project.build.projectDirectory=/home/mbaudier/dev/src/commons/org.argeo.server.jcr
+version.rap=2.2.0-R-20131204-0942
+sun.java.launcher=SUN_STANDARD
+pom.inceptionYear=2007
+env.IMSETTINGS_INTEGRATE_DESKTOP=yes
+project.packaging=jar
+java.vm.specification.vendor=Oracle Corporation
+file.separator=/
+java.runtime.version=1.7.0_71-mockbuild_2014_10_17_22_23-b00
+project.pomFile=/home/mbaudier/dev/src/commons/org.argeo.server.jcr/pom.xml
+env.SSH_AUTH_SOCK=/tmp/keyring-YLbPHE/socket.ssh
+sun.boot.class.path=/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/resources.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/rt.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/sunrsasign.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/jsse.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/jce.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/charsets.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/rhino.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/lib/jfr.jar\:/usr/lib/jvm/java-1.7.0-openjdk-1.7.0.71.x86_64/jre/classes
+project.build.packaging=jar
+maven.version=3.2.3
+project.artifactId=org.argeo.server.jcr
+env.QTINC=/usr/lib64/qt-3.3/include
+user.country=GB
+pom.modelEncoding=UTF-8
+maven.home=/opt/apache-maven
+env.SHELL=/bin/bash
+Bundle-RequiredExecutionEnvironment=JavaSE-1.6
+-sourcepath=/home/mbaudier/dev/src/commons/org.argeo.server.jcr/src/main/java,/home/mbaudier/dev/src/commons/org.argeo.server.jcr/target/classes
+pom.groupId=org.argeo.commons
+java.vendor=Oracle Corporation
+env.GPG_AGENT_INFO=/tmp/seahorse-CQZHIM/S.gpg-agent\:17621\:1;
+env.IMSETTINGS_MODULE=none
+Bundle-Name=Commons Server JCR
+java.specification.version=1.7
+env.GNOME_DESKTOP_SESSION_ID=this-is-deprecated
+sun.arch.data.model=64
+#-----------------------------------------------------------------------
diff --git a/org.argeo.server.jcr/ext/test/log4j.properties b/org.argeo.server.jcr/ext/test/log4j.properties
new file mode 100644 (file)
index 0000000..ca995af
--- /dev/null
@@ -0,0 +1,13 @@
+log4j.rootLogger=WARN, console
+
+## Levels
+log4j.logger.org.argeo=DEBUG
+log4j.logger.org.apache.jackrabbit=OFF
+
+## Appenders
+# console is set to be a ConsoleAppender.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+
+# console uses PatternLayout.
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java
new file mode 100644 (file)
index 0000000..23281d0
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.io.File;
+
+import javax.jcr.Repository;
+
+import org.apache.jackrabbit.core.TransientRepository;
+import org.argeo.jcr.unit.AbstractJcrTestCase;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+/** Factorizes configuration of an in memory transient repository */
+public abstract class AbstractInternalJackrabbitTestCase extends
+               AbstractJcrTestCase {
+       protected File getRepositoryFile() throws Exception {
+               Resource res = new ClassPathResource(
+                               "org/argeo/server/jcr/repository-memory.xml");
+               return res.getFile();
+       }
+
+       protected Repository createRepository() throws Exception {
+               Repository repository = new TransientRepository(getRepositoryFile(),
+                               getHomeDir());
+               return repository;
+       }
+
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/CollectionsObject.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/CollectionsObject.java
new file mode 100644 (file)
index 0000000..1cbb931
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class CollectionsObject {
+       private String id;
+       private String label;
+       private SimpleObject simpleObject;
+       private List<String> stringList = new ArrayList<String>();
+       private Map<String, Float> floatMap = new HashMap<String, Float>();
+       private Map<SimpleObject, String> objectMap = new HashMap<SimpleObject, String>();
+       private Map<String, Map<String, String>> mapOfMaps = new HashMap<String, Map<String, String>>();
+
+       public String getId() {
+               return id;
+       }
+
+       public void setId(String id) {
+               this.id = id;
+       }
+
+       public String getLabel() {
+               return label;
+       }
+
+       public void setLabel(String label) {
+               this.label = label;
+       }
+
+       public SimpleObject getSimpleObject() {
+               return simpleObject;
+       }
+
+       public void setSimpleObject(SimpleObject simpleObject) {
+               this.simpleObject = simpleObject;
+       }
+
+       public List<String> getStringList() {
+               return stringList;
+       }
+
+       public void setStringList(List<String> stringList) {
+               this.stringList = stringList;
+       }
+
+       public Map<String, Float> getFloatMap() {
+               return floatMap;
+       }
+
+       public void setFloatMap(Map<String, Float> floatMap) {
+               this.floatMap = floatMap;
+       }
+
+       public Map<SimpleObject, String> getObjectMap() {
+               return objectMap;
+       }
+
+       public void setObjectMap(Map<SimpleObject, String> objectMap) {
+               this.objectMap = objectMap;
+       }
+
+       public Map<String, Map<String, String>> getMapOfMaps() {
+               return mapOfMaps;
+       }
+
+       public void setMapOfMaps(Map<String, Map<String, String>> mapOfMaps) {
+               this.mapOfMaps = mapOfMaps;
+       }
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/MapperTest.java
new file mode 100644 (file)
index 0000000..5486ff4
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Node;
+
+import org.argeo.jcr.spring.BeanNodeMapper;
+
+public class MapperTest extends AbstractInternalJackrabbitTestCase {
+       public void testSimpleObject() throws Exception {
+               SimpleObject mySo = new SimpleObject();
+               mySo.setInteger(100);
+               mySo.setString("hello world");
+
+               OtherObject oo1 = new OtherObject();
+               oo1.setKey("someKey");
+               oo1.setValue("stringValue");
+               mySo.setOtherObject(oo1);
+
+               OtherObject oo2 = new OtherObject();
+               oo2.setKey("anotherSimpleObject");
+               oo2.setValue(new SimpleObject());
+               mySo.setAnotherObject(oo2);
+
+               BeanNodeMapper bnm = new BeanNodeMapper();
+
+               Node node = bnm.save(session(), mySo);
+               session().save();
+               JcrUtils.debug(node);
+       }
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/OtherObject.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/OtherObject.java
new file mode 100644 (file)
index 0000000..5cce8ff
--- /dev/null
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+public class OtherObject {
+       private String key;
+       private Object value;
+
+       public String getKey() {
+               return key;
+       }
+
+       public void setKey(String key) {
+               this.key = key;
+       }
+
+       public Object getValue() {
+               return value;
+       }
+
+       public void setValue(Object value) {
+               this.value = value;
+       }
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/SimpleObject.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/SimpleObject.java
new file mode 100644 (file)
index 0000000..cdc32fc
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.UUID;
+
+public class SimpleObject {
+       private String string;
+       private String uuid = UUID.randomUUID().toString();
+       private Integer integer;
+       private OtherObject otherObject;
+       private OtherObject anotherObject;
+
+       public String getString() {
+               return string;
+       }
+
+       public void setString(String sting) {
+               this.string = sting;
+       }
+
+       public Integer getInteger() {
+               return integer;
+       }
+
+       public void setInteger(Integer integer) {
+               this.integer = integer;
+       }
+
+       public OtherObject getOtherObject() {
+               return otherObject;
+       }
+
+       public void setOtherObject(OtherObject otherObject) {
+               this.otherObject = otherObject;
+       }
+
+       public OtherObject getAnotherObject() {
+               return anotherObject;
+       }
+
+       public void setAnotherObject(OtherObject anotherObject) {
+               this.anotherObject = anotherObject;
+       }
+
+       @Override
+       public boolean equals(Object obj) {
+               return string.equals(((SimpleObject) obj).string);
+       }
+
+       @Override
+       public int hashCode() {
+               return string.hashCode();
+       }
+
+       public void setUuid(String uuid) {
+               this.uuid = uuid;
+       }
+
+       public String getUuid() {
+               return uuid;
+       }
+
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/jcr/tabular/JcrTabularTest.java b/org.argeo.server.jcr/ext/test/org/argeo/jcr/tabular/JcrTabularTest.java
new file mode 100644 (file)
index 0000000..2045f9a
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.tabular;
+
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.commons.cnd.CndImporter;
+import org.argeo.jcr.AbstractInternalJackrabbitTestCase;
+import org.argeo.jcr.ArgeoNames;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.util.tabular.TabularColumn;
+import org.argeo.util.tabular.TabularRow;
+import org.argeo.util.tabular.TabularRowIterator;
+import org.argeo.util.tabular.TabularWriter;
+
+public class JcrTabularTest extends AbstractInternalJackrabbitTestCase {
+       private final static Log log = LogFactory.getLog(JcrTabularTest.class);
+
+       public void testWriteReadCsv() throws Exception {
+               session().setNamespacePrefix("argeo", ArgeoNames.ARGEO_NAMESPACE);
+               InputStreamReader reader = new InputStreamReader(getClass()
+                               .getResourceAsStream("/org/argeo/jcr/argeo.cnd"));
+               CndImporter.registerNodeTypes(reader, session());
+               reader.close();
+
+               // write
+               Integer columnCount = 15;
+               Long rowCount = 1000l;
+               String stringValue = "test, \ntest";
+
+               List<TabularColumn> header = new ArrayList<TabularColumn>();
+               for (int i = 0; i < columnCount; i++) {
+                       header.add(new TabularColumn("col" + i, PropertyType.STRING));
+               }
+               Node tableNode = session().getRootNode().addNode("table",
+                               ArgeoTypes.ARGEO_TABLE);
+               TabularWriter writer = new JcrTabularWriter(tableNode, header,
+                               ArgeoTypes.ARGEO_CSV);
+               for (int i = 0; i < rowCount; i++) {
+                       List<Object> objs = new ArrayList<Object>();
+                       for (int j = 0; j < columnCount; j++) {
+                               objs.add(stringValue);
+                       }
+                       writer.appendRow(objs.toArray());
+               }
+               writer.close();
+               session().save();
+
+               if (log.isDebugEnabled())
+                       log.debug("Wrote tabular content " + rowCount + " rows, "
+                                       + columnCount + " columns");
+               // read
+               TabularRowIterator rowIt = new JcrTabularRowIterator(tableNode);
+               Long count = 0l;
+               while (rowIt.hasNext()) {
+                       TabularRow tr = rowIt.next();
+                       assertEquals(header.size(), tr.size());
+                       count++;
+               }
+               assertEquals(rowCount, count);
+               if (log.isDebugEnabled())
+                       log.debug("Read tabular content " + rowCount + " rows, "
+                                       + columnCount + " columns");
+       }
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/JcrResourceAdapterTest.java b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/JcrResourceAdapterTest.java
new file mode 100644 (file)
index 0000000..b691572
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.server.jcr;
+
+import java.io.InputStream;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.List;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.jcr.AbstractInternalJackrabbitTestCase;
+import org.argeo.jcr.JcrResourceAdapter;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+
+public class JcrResourceAdapterTest extends AbstractInternalJackrabbitTestCase {
+       private static SimpleDateFormat sdf = new SimpleDateFormat(
+                       "yyyyMMdd:hhmmss.SSS");
+
+       private final static Log log = LogFactory
+                       .getLog(JcrResourceAdapterTest.class);
+
+       private JcrResourceAdapter jra;
+
+       public void testCreate() throws Exception {
+               String basePath = "/test/subdir";
+               jra.mkdirs(basePath);
+               Resource res = new ClassPathResource("org/argeo/server/jcr/dummy00.xls");
+               String filePath = basePath + "/dummy.xml";
+               jra.create(filePath, res.getInputStream(), "application/vnd.ms-excel");
+               InputStream in = jra.retrieve(filePath);
+               assertTrue(IOUtils.contentEquals(res.getInputStream(), in));
+       }
+
+       public void testVersioning() throws Exception {
+               String basePath = "/test/versions";
+               jra.mkdirs(basePath);
+               String filePath = basePath + "/dummy.xml";
+               Resource res00 = new ClassPathResource(
+                               "org/argeo/server/jcr/dummy00.xls");
+               jra.create(filePath, res00.getInputStream(), "application/vnd.ms-excel");
+               Resource res01 = new ClassPathResource(
+                               "org/argeo/server/jcr/dummy01.xls");
+               jra.update(filePath, res01.getInputStream());
+               Resource res02 = new ClassPathResource(
+                               "org/argeo/server/jcr/dummy02.xls");
+               jra.update(filePath, res02.getInputStream());
+
+               List<Calendar> versions = jra.listVersions(filePath);
+               log.debug("Versions of " + filePath);
+               int count = 0;
+               for (Calendar version : versions) {
+                       log.debug(" " + (count == 0 ? "base" : count - 1) + "\t"
+                                       + sdf.format(version.getTime()));
+                       count++;
+               }
+
+               assertEquals(4, versions.size());
+
+               InputStream in = jra.retrieve(filePath, 1);
+               assertTrue(IOUtils.contentEquals(res01.getInputStream(), in));
+               in = jra.retrieve(filePath, 0);
+               assertTrue(IOUtils.contentEquals(res00.getInputStream(), in));
+               in = jra.retrieve(filePath, 2);
+               assertTrue(IOUtils.contentEquals(res02.getInputStream(), in));
+               Resource res03 = new ClassPathResource(
+                               "org/argeo/server/jcr/dummy03.xls");
+               jra.update(filePath, res03.getInputStream());
+               in = jra.retrieve(filePath, 1);
+               assertTrue(IOUtils.contentEquals(res01.getInputStream(), in));
+       }
+
+       @Override
+       protected void setUp() throws Exception {
+               log.debug("SET UP");
+               super.setUp();
+               jra = new JcrResourceAdapter();
+               jra.setSession(session());
+       }
+
+       @Override
+       protected void tearDown() throws Exception {
+               log.debug("TEAR DOWN");
+               super.tearDown();
+       }
+}
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy00.xls b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy00.xls
new file mode 100644 (file)
index 0000000..e5846fe
Binary files /dev/null and b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy00.xls differ
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy01.xls b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy01.xls
new file mode 100644 (file)
index 0000000..b5c6b55
Binary files /dev/null and b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy01.xls differ
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy02.xls b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy02.xls
new file mode 100644 (file)
index 0000000..d73bc66
Binary files /dev/null and b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy02.xls differ
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy03.xls b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy03.xls
new file mode 100644 (file)
index 0000000..0759cb9
Binary files /dev/null and b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/dummy03.xls differ
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/repository-h2.xml b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/repository-h2.xml
new file mode 100644 (file)
index 0000000..ef3f0c4
--- /dev/null
@@ -0,0 +1,98 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2007-2012 Argeo GmbH
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+       <!-- Shared datasource -->
+       <DataSources>
+               <DataSource name="dataSource">
+                       <param name="driver" value="org.h2.Driver" />
+                       <param name="url" value="jdbc:h2:mem:jackrabbit" />
+                       <param name="user" value="sa" />
+                       <param name="password" value="" />
+                       <param name="databaseType" value="h2" />
+                       <param name="maxPoolSize" value="10" />
+               </DataSource>
+       </DataSources>
+
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+               <param name="dataSourceName" value="dataSource" />
+               <param name="schema" value="default" />
+               <param name="schemaObjectPrefix" value="fs_" />
+       </FileSystem>
+       <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
+               <param name="dataSourceName" value="dataSource" />
+               <param name="schemaObjectPrefix" value="ds_" />
+       </DataStore>
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="dev" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schema" value="default" />
+                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
+               </FileSystem>
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${wsp.home}/index" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schema" value="default" />
+                       <param name="schemaObjectPrefix" value="fs_ver_" />
+               </FileSystem>
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
+                       <param name="dataSourceName" value="dataSource" />
+                       <param name="schemaObjectPrefix" value="pm_ver_" />
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/repository/index" />
+               <param name="extractorPoolSize" value="2" />
+               <param name="supportHighlighting" value="true" />
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <SecurityManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+                       workspaceName="security" />
+               <AccessManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+               <LoginModule
+                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+                       <param name="anonymousId" value="anonymous" />
+                       <param name="adminId" value="admin" />
+               </LoginModule>
+       </Security>
+</Repository>
\ No newline at end of file
diff --git a/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/repository-memory.xml b/org.argeo.server.jcr/ext/test/org/argeo/server/jcr/repository-memory.xml
new file mode 100644 (file)
index 0000000..8395424
--- /dev/null
@@ -0,0 +1,72 @@
+<?xml version="1.0"?>
+<!--
+
+    Copyright (C) 2007-2012 Argeo GmbH
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+
+-->
+<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
+                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
+<Repository>
+       <!-- File system and datastore -->
+       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+
+       <!-- Workspace templates -->
+       <Workspaces rootPath="${rep.home}/workspaces"
+               defaultWorkspace="main" configRootPath="/workspaces" />
+       <Workspace name="${wsp.name}">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+                       <param name="path" value="${rep.home}/repository/index" />
+                       <param name="directoryManagerClass"
+                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               </SearchIndex>
+       </Workspace>
+
+       <!-- Versioning -->
+       <Versioning rootPath="${rep.home}/version">
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+               <PersistenceManager
+                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
+                       <param name="blobFSBlockSize" value="1" />
+               </PersistenceManager>
+       </Versioning>
+
+       <!-- Indexing -->
+       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+               <param name="path" value="${rep.home}/repository/index" />
+               <param name="directoryManagerClass"
+                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
+               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
+       </SearchIndex>
+
+       <!-- Security -->
+       <Security appName="Jackrabbit">
+               <SecurityManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
+                       workspaceName="security" />
+               <AccessManager
+                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
+               <LoginModule
+                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
+                       <param name="anonymousId" value="anonymous" />
+                       <param name="adminId" value="admin" />
+               </LoginModule>
+       </Security>
+</Repository>
\ No newline at end of file
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrConstants.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrConstants.java
new file mode 100644 (file)
index 0000000..b9b513a
--- /dev/null
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+/** Argeo model specific constants */
+public interface ArgeoJcrConstants {
+       public final static String ARGEO_BASE_PATH = "/argeo:system";
+       public final static String DATA_MODELS_BASE_PATH = ARGEO_BASE_PATH
+                       + "/argeo:dataModels";
+       public final static String PEOPLE_BASE_PATH = ARGEO_BASE_PATH
+                       + "/argeo:people";
+
+       // parameters (typically for call to a RepositoryFactory)
+       public final static String JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias";
+       public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
+
+       // standard aliases
+       public final static String ALIAS_NODE = "node";
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrUtils.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrUtils.java
new file mode 100644 (file)
index 0000000..dccb06c
--- /dev/null
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.argeo.ArgeoException;
+
+/** Utilities related to Argeo model in JCR */
+public class ArgeoJcrUtils implements ArgeoJcrConstants {
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it
+        * and protect against future API changes.
+        */
+       public static Repository getRepositoryByAlias(
+                       RepositoryFactory repositoryFactory, String alias) {
+               try {
+                       Map<String, String> parameters = new HashMap<String, String>();
+                       parameters.put(JCR_REPOSITORY_ALIAS, alias);
+                       return repositoryFactory.getRepository(parameters);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Unexpected exception when trying to retrieve repository with alias "
+                                                       + alias, e);
+               }
+       }
+
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and
+        * protect against future API changes.
+        */
+       public static Repository getRepositoryByUri(
+                       RepositoryFactory repositoryFactory, String uri) {
+               return getRepositoryByUri(repositoryFactory, uri, null);
+       }
+
+       /**
+        * Wraps the call to the repository factory based on parameter
+        * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and
+        * protect against future API changes.
+        */
+       public static Repository getRepositoryByUri(
+                       RepositoryFactory repositoryFactory, String uri, String alias) {
+               try {
+                       Map<String, String> parameters = new HashMap<String, String>();
+                       parameters.put(JCR_REPOSITORY_URI, uri);
+                       if (alias != null)
+                               parameters.put(JCR_REPOSITORY_ALIAS, alias);
+                       return repositoryFactory.getRepository(parameters);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Unexpected exception when trying to retrieve repository with uri "
+                                                       + uri, e);
+               }
+       }
+
+       private ArgeoJcrUtils() {
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoNames.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoNames.java
new file mode 100644 (file)
index 0000000..6e3eca9
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+/** JCR names in the http://www.argeo.org/argeo namespace */
+public interface ArgeoNames {
+       public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo";
+       public final static String ARGEO = "argeo";
+
+       public final static String ARGEO_URI = "argeo:uri";
+       public final static String ARGEO_USER_ID = "argeo:userID";
+       public final static String ARGEO_PREFERENCES = "argeo:preferences";
+       public final static String ARGEO_DATA_MODEL_VERSION = "argeo:dataModelVersion";
+
+       public final static String ARGEO_REMOTE = "argeo:remote";
+       public final static String ARGEO_PASSWORD = "argeo:password";
+       public final static String ARGEO_REMOTE_ROLES = "argeo:remoteRoles";
+
+       // user profile
+       public final static String ARGEO_PROFILE = "argeo:profile";
+
+       // spring security
+       public final static String ARGEO_ENABLED = "argeo:enabled";
+       public final static String ARGEO_ACCOUNT_NON_EXPIRED = "argeo:accountNonExpired";
+       public final static String ARGEO_ACCOUNT_NON_LOCKED = "argeo:accountNonLocked";
+       public final static String ARGEO_CREDENTIALS_NON_EXPIRED = "argeo:credentialsNonExpired";
+
+       // personal details
+       public final static String ARGEO_FIRST_NAME = "argeo:firstName";
+       public final static String ARGEO_LAST_NAME = "argeo:lastName";
+       public final static String ARGEO_PRIMARY_EMAIL = "argeo:primaryEmail";
+       public final static String ARGEO_PRIMARY_ORGANIZATION = "argeo:primaryOrganization";
+
+       // tabular
+       public final static String ARGEO_IS_KEY = "argeo:isKey";
+
+       // crypto
+       public final static String ARGEO_IV = "argeo:iv";
+       public final static String ARGEO_SECRET_KEY_FACTORY = "argeo:secretKeyFactory";
+       public final static String ARGEO_SALT = "argeo:salt";
+       public final static String ARGEO_ITERATION_COUNT = "argeo:iterationCount";
+       public final static String ARGEO_KEY_LENGTH = "argeo:keyLength";
+       public final static String ARGEO_SECRET_KEY_ENCRYPTION = "argeo:secretKeyEncryption";
+       public final static String ARGEO_CIPHER = "argeo:cipher";
+       public final static String ARGEO_KEYRING = "argeo:keyring";
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoTypes.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoTypes.java
new file mode 100644 (file)
index 0000000..a11ead5
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+/** JCR types in the http://www.argeo.org/argeo namespace */
+public interface ArgeoTypes {
+       public final static String ARGEO_LINK = "argeo:link";
+       public final static String ARGEO_USER_HOME = "argeo:userHome";
+       public final static String ARGEO_USER_PROFILE = "argeo:userProfile";
+       public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository";
+       public final static String ARGEO_PREFERENCE_NODE = "argeo:preferenceNode";
+
+       // data model
+       public final static String ARGEO_DATA_MODEL = "argeo:dataModel";
+       
+       // tabular
+       public final static String ARGEO_TABLE = "argeo:table";
+       public final static String ARGEO_COLUMN = "argeo:column";
+       public final static String ARGEO_CSV = "argeo:csv";
+
+       // crypto
+       public final static String ARGEO_ENCRYPTED = "argeo:encrypted";
+       public final static String ARGEO_PBE_SPEC = "argeo:pbeSpec";
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/CollectionNodeIterator.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/CollectionNodeIterator.java
new file mode 100644 (file)
index 0000000..a65907a
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+
+/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
+public class CollectionNodeIterator implements NodeIterator {
+       private final Long collectionSize;
+       private final Iterator<Node> iterator;
+       private Integer position = 0;
+
+       public CollectionNodeIterator(Collection<Node> nodes) {
+               super();
+               this.collectionSize = (long) nodes.size();
+               this.iterator = nodes.iterator();
+       }
+
+       public void skip(long skipNum) {
+               if (skipNum < 0)
+                       throw new IllegalArgumentException(
+                                       "Skip count has to be positive: " + skipNum);
+
+               for (long i = 0; i < skipNum; i++) {
+                       if (!hasNext())
+                               throw new NoSuchElementException("Last element past (position="
+                                               + getPosition() + ")");
+                       nextNode();
+               }
+       }
+
+       public long getSize() {
+               return collectionSize;
+       }
+
+       public long getPosition() {
+               return position;
+       }
+
+       public boolean hasNext() {
+               return iterator.hasNext();
+       }
+
+       public Object next() {
+               return nextNode();
+       }
+
+       public void remove() {
+               iterator.remove();
+       }
+
+       public Node nextNode() {
+               Node node = iterator.next();
+               position++;
+               return node;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultJcrListener.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultJcrListener.java
new file mode 100644 (file)
index 0000000..253b305
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+
+/** To be overridden */
+public class DefaultJcrListener implements EventListener {
+       private final static Log log = LogFactory.getLog(DefaultJcrListener.class);
+       private Session session;
+       private String path = "/";
+       private Boolean deep = true;
+
+       public void start() {
+               try {
+                       addEventListener(session().getWorkspace().getObservationManager());
+                       if (log.isDebugEnabled())
+                               log.debug("Registered JCR event listener on " + path);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot register event listener", e);
+               }
+       }
+
+       public void stop() {
+               try {
+                       session().getWorkspace().getObservationManager()
+                                       .removeEventListener(this);
+                       if (log.isDebugEnabled())
+                               log.debug("Unregistered JCR event listener on " + path);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot unregister event listener", e);
+               }
+       }
+
+       /** Default is listen to all events */
+       protected Integer getEvents() {
+               return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
+                               | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
+       }
+
+       /** To be overidden */
+       public void onEvent(EventIterator events) {
+               while (events.hasNext()) {
+                       Event event = events.nextEvent();
+                       log.debug(event);
+               }
+       }
+
+       /** To be overidden */
+       protected void addEventListener(ObservationManager observationManager)
+                       throws RepositoryException {
+               observationManager.addEventListener(this, getEvents(), path, deep,
+                               null, null, false);
+       }
+
+       private Session session() {
+               return session;
+       }
+
+       public void setPath(String path) {
+               this.path = path;
+       }
+
+       public void setDeep(Boolean deep) {
+               this.deep = deep;
+       }
+
+       public void setSession(Session session) {
+               this.session = session;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryFactory.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryFactory.java
new file mode 100644 (file)
index 0000000..a7b30e7
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Map;
+import java.util.Properties;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.RepositoryFactory;
+
+import org.argeo.ArgeoException;
+
+/**
+ * Simple implementation of {@link RepositoryFactory}, supporting OSGi aliases.
+ */
+public class DefaultRepositoryFactory extends DefaultRepositoryRegister
+               implements RepositoryFactory, ArgeoJcrConstants {
+       @SuppressWarnings("rawtypes")
+       public Repository getRepository(Map parameters) throws RepositoryException {
+               if (parameters.containsKey(JCR_REPOSITORY_ALIAS)) {
+                       String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString();
+                       return getRepositoryByAlias(alias);
+               } else if (parameters.containsKey(JCR_REPOSITORY_URI)) {
+                       String uri = parameters.get(JCR_REPOSITORY_URI).toString();
+                       return getRepositoryByAlias(getAliasFromURI(uri));
+               }
+               return null;
+       }
+
+       protected String getAliasFromURI(String uri) {
+               try {
+                       URI uriObj = new URI(uri);
+                       String alias = uriObj.getPath();
+                       if (alias.charAt(0) == '/')
+                               alias = alias.substring(1);
+                       if (alias.charAt(alias.length() - 1) == '/')
+                               alias = alias.substring(0, alias.length() - 1);
+                       return alias;
+               } catch (URISyntaxException e) {
+                       throw new ArgeoException("Cannot interpret URI " + uri, e);
+               }
+       }
+
+       /**
+        * Retrieve a repository by alias
+        * 
+        * @return the repository registered with alias or null if none
+        */
+       protected Repository getRepositoryByAlias(String alias) {
+               if (getRepositories().containsKey(alias))
+                       return getRepositories().get(alias);
+               else
+                       return null;
+       }
+
+       protected void publish(String alias, Repository repository,
+                       Properties properties) {
+               register(repository, properties);
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryRegister.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryRegister.java
new file mode 100644 (file)
index 0000000..f13c84e
--- /dev/null
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.Collections;
+import java.util.Map;
+import java.util.Observable;
+import java.util.TreeMap;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+public class DefaultRepositoryRegister extends Observable implements
+               RepositoryRegister, ArgeoJcrConstants {
+       private final static Log log = LogFactory
+                       .getLog(DefaultRepositoryRegister.class);
+
+       /** Read only map which will be directly exposed. */
+       private Map<String, Repository> repositories = Collections
+                       .unmodifiableMap(new TreeMap<String, Repository>());
+
+       @SuppressWarnings("rawtypes")
+       public synchronized Repository getRepository(Map parameters)
+                       throws RepositoryException {
+               if (!parameters.containsKey(JCR_REPOSITORY_ALIAS))
+                       throw new RepositoryException("Parameter " + JCR_REPOSITORY_ALIAS
+                                       + " has to be defined.");
+               String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString();
+               if (!repositories.containsKey(alias))
+                       throw new RepositoryException(
+                                       "No repository registered with alias " + alias);
+
+               return repositories.get(alias);
+       }
+
+       /** Access to the read-only map */
+       public synchronized Map<String, Repository> getRepositories() {
+               return repositories;
+       }
+
+       /** Registers a service, typically called when OSGi services are bound. */
+       @SuppressWarnings("rawtypes")
+       public synchronized void register(Repository repository, Map properties) {
+               // TODO: also check bean name?
+               String alias;
+               if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) {
+                       log.warn("Cannot register a repository if no "
+                                       + JCR_REPOSITORY_ALIAS + " property is speecified.");
+                       return;
+               }
+               alias = properties.get(JCR_REPOSITORY_ALIAS).toString();
+               Map<String, Repository> map = new TreeMap<String, Repository>(
+                               repositories);
+               map.put(alias, repository);
+               repositories = Collections.unmodifiableMap(map);
+               setChanged();
+               notifyObservers(alias);
+       }
+
+       /** Unregisters a service, typically called when OSGi services are unbound. */
+       @SuppressWarnings("rawtypes")
+       public synchronized void unregister(Repository repository, Map properties) {
+               // TODO: also check bean name?
+               if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) {
+                       log.warn("Cannot unregister a repository without property "
+                                       + JCR_REPOSITORY_ALIAS);
+                       return;
+               }
+
+               String alias = properties.get(JCR_REPOSITORY_ALIAS).toString();
+               Map<String, Repository> map = new TreeMap<String, Repository>(
+                               repositories);
+               map.put(alias, repository);
+               repositories = Collections.unmodifiableMap(map);
+               setChanged();
+               notifyObservers(alias);
+       }
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrCallback.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrCallback.java
new file mode 100644 (file)
index 0000000..0c4706f
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Session;
+
+/** An arbitrary execution on a JCR session, optionally returning a result. */
+public interface JcrCallback {
+       public Object execute(Session session);
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrRepositoryWrapper.java
new file mode 100644 (file)
index 0000000..f993c2f
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Credentials;
+import javax.jcr.LoginException;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+
+import org.argeo.ArgeoException;
+
+/**
+ * Wrapper around a JCR repository which allows to simplify configuration and
+ * intercept some actions. It exposes itself as a {@link Repository}.
+ */
+public abstract class JcrRepositoryWrapper implements Repository {
+       // private final static Log log = LogFactory
+       // .getLog(JcrRepositoryWrapper.class);
+
+       // wrapped repository
+       private Repository repository;
+
+       private Boolean autocreateWorkspaces = false;
+
+       /**
+        * Empty constructor, {@link #init()} should be called after properties have
+        * been set
+        */
+       public JcrRepositoryWrapper() {
+       }
+
+       /** Initializes */
+       public void init() {
+       }
+
+       /** Shutdown the repository */
+       public void destroy() throws Exception {
+       }
+
+       /*
+        * DELEGATED JCR REPOSITORY METHODS
+        */
+
+       public String getDescriptor(String key) {
+               return getRepository().getDescriptor(key);
+       }
+
+       public String[] getDescriptorKeys() {
+               return getRepository().getDescriptorKeys();
+       }
+
+       /** Central login method */
+       public Session login(Credentials credentials, String workspaceName)
+                       throws LoginException, NoSuchWorkspaceException,
+                       RepositoryException {
+               Session session;
+               try {
+                       session = getRepository().login(credentials, workspaceName);
+               } catch (NoSuchWorkspaceException e) {
+                       if (autocreateWorkspaces && workspaceName != null)
+                               session = createWorkspaceAndLogsIn(credentials, workspaceName);
+                       else
+                               throw e;
+               }
+               processNewSession(session);
+               return session;
+       }
+
+       public Session login() throws LoginException, RepositoryException {
+               return login(null, null);
+       }
+
+       public Session login(Credentials credentials) throws LoginException,
+                       RepositoryException {
+               return login(credentials, null);
+       }
+
+       public Session login(String workspaceName) throws LoginException,
+                       NoSuchWorkspaceException, RepositoryException {
+               return login(null, workspaceName);
+       }
+
+       /** Called after a session has been created, does nothing by default. */
+       protected void processNewSession(Session session) {
+       }
+
+       /** Wraps access to the repository, making sure it is available. */
+       protected synchronized Repository getRepository() {
+//             if (repository == null) {
+//                     throw new ArgeoException("No repository initialized."
+//                                     + " Was the init() method called?"
+//                                     + " The destroy() method should also"
+//                                     + " be called on shutdown.");
+//             }
+               return repository;
+       }
+
+       /**
+        * Logs in to the default workspace, creates the required workspace, logs
+        * out, logs in to the required workspace.
+        */
+       protected Session createWorkspaceAndLogsIn(Credentials credentials,
+                       String workspaceName) throws RepositoryException {
+               if (workspaceName == null)
+                       throw new ArgeoException("No workspace specified.");
+               Session session = getRepository().login(credentials);
+               session.getWorkspace().createWorkspace(workspaceName);
+               session.logout();
+               return getRepository().login(credentials, workspaceName);
+       }
+
+       public boolean isStandardDescriptor(String key) {
+               return getRepository().isStandardDescriptor(key);
+       }
+
+       public boolean isSingleValueDescriptor(String key) {
+               return getRepository().isSingleValueDescriptor(key);
+       }
+
+       public Value getDescriptorValue(String key) {
+               return getRepository().getDescriptorValue(key);
+       }
+
+       public Value[] getDescriptorValues(String key) {
+               return getRepository().getDescriptorValues(key);
+       }
+
+       public synchronized void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
+               this.autocreateWorkspaces = autocreateWorkspaces;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrResourceAdapter.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrResourceAdapter.java
new file mode 100644 (file)
index 0000000..0b1a98c
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.version.Version;
+import javax.jcr.version.VersionHistory;
+import javax.jcr.version.VersionIterator;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+
+/**
+ * Bridge Spring resources and JCR folder / files semantics (nt:folder /
+ * nt:file), supporting versioning as well.
+ */
+public class JcrResourceAdapter {
+       private final static Log log = LogFactory.getLog(JcrResourceAdapter.class);
+
+       private Session session;
+
+       private Boolean versioning = true;
+       private String defaultEncoding = "UTF-8";
+
+       // private String restoreBase = "/.restore";
+
+       public JcrResourceAdapter() {
+       }
+
+       public JcrResourceAdapter(Session session) {
+               this.session = session;
+       }
+
+       public void mkdirs(String path) {
+               JcrUtils.mkdirs(session(), path, NodeType.NT_FOLDER,
+                               NodeType.NT_FOLDER, versioning);
+       }
+
+       public void create(String path, InputStream in, String mimeType) {
+               try {
+                       if (session().itemExists(path)) {
+                               throw new ArgeoException("Node " + path + " already exists.");
+                       }
+
+                       int index = path.lastIndexOf('/');
+                       String parentPath = path.substring(0, index);
+                       if (parentPath.equals(""))
+                               parentPath = "/";
+                       String fileName = path.substring(index + 1);
+                       if (!session().itemExists(parentPath))
+                               throw new ArgeoException("Parent folder of node " + path
+                                               + " does not exist: " + parentPath);
+
+                       Node folderNode = (Node) session().getItem(parentPath);
+                       Node fileNode = folderNode.addNode(fileName, "nt:file");
+
+                       Node contentNode = fileNode.addNode(Property.JCR_CONTENT,
+                                       "nt:resource");
+                       if (mimeType != null)
+                               contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
+                       contentNode.setProperty(Property.JCR_ENCODING, defaultEncoding);
+                       Binary binary = session().getValueFactory().createBinary(in);
+                       contentNode.setProperty(Property.JCR_DATA, binary);
+                       JcrUtils.closeQuietly(binary);
+                       Calendar lastModified = Calendar.getInstance();
+                       // lastModified.setTimeInMillis(file.lastModified());
+                       contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified);
+                       // resNode.addMixin("mix:referenceable");
+
+                       if (versioning)
+                               fileNode.addMixin("mix:versionable");
+
+                       session().save();
+
+                       if (versioning)
+                               session().getWorkspace().getVersionManager()
+                                               .checkin(fileNode.getPath());
+
+                       if (log.isDebugEnabled())
+                               log.debug("Created " + path);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot create node for " + path, e);
+               }
+
+       }
+
+       public void update(String path, InputStream in) {
+               try {
+
+                       if (!session().itemExists(path)) {
+                               String type = null;
+                               // FIXME: using javax.activation leads to conflict between Java
+                               // 1.5 and 1.6 (since javax.activation was included in Java 1.6)
+                               // String type = new MimetypesFileTypeMap()
+                               // .getContentType(FilenameUtils.getName(path));
+                               create(path, in, type);
+                               return;
+                       }
+
+                       Node fileNode = (Node) session().getItem(path);
+                       Node contentNode = fileNode.getNode(Property.JCR_CONTENT);
+                       if (versioning)
+                               session().getWorkspace().getVersionManager()
+                                               .checkout(fileNode.getPath());
+                       Binary binary = session().getValueFactory().createBinary(in);
+                       contentNode.setProperty(Property.JCR_DATA, binary);
+                       JcrUtils.closeQuietly(binary);
+                       Calendar lastModified = Calendar.getInstance();
+                       // lastModified.setTimeInMillis(file.lastModified());
+                       contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified);
+
+                       session().save();
+                       if (versioning)
+                               session().getWorkspace().getVersionManager()
+                                               .checkin(fileNode.getPath());
+
+                       if (log.isDebugEnabled())
+                               log.debug("Updated " + path);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot update node " + path, e);
+               }
+       }
+
+       public List<Calendar> listVersions(String path) {
+               if (!versioning)
+                       throw new ArgeoException("Versioning is not activated");
+
+               try {
+                       List<Calendar> versions = new ArrayList<Calendar>();
+                       Node fileNode = (Node) session().getItem(path);
+                       VersionHistory history = session().getWorkspace()
+                                       .getVersionManager().getVersionHistory(fileNode.getPath());
+                       for (VersionIterator it = history.getAllVersions(); it.hasNext();) {
+                               Version version = (Version) it.next();
+                               versions.add(version.getCreated());
+                               if (log.isTraceEnabled()) {
+                                       log.debug(version);
+                                       // debug(version);
+                               }
+                       }
+                       return versions;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot list version of node " + path, e);
+               }
+       }
+
+       public InputStream retrieve(String path) {
+               try {
+                       Node node = (Node) session().getItem(
+                                       path + "/" + Property.JCR_CONTENT);
+                       Property property = node.getProperty(Property.JCR_DATA);
+                       return property.getBinary().getStream();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot retrieve " + path, e);
+               }
+       }
+
+       public synchronized InputStream retrieve(String path, Integer revision) {
+               if (!versioning)
+                       throw new ArgeoException("Versioning is not activated");
+
+               try {
+                       Node fileNode = (Node) session().getItem(path);
+                       VersionHistory history = session().getWorkspace()
+                                       .getVersionManager().getVersionHistory(fileNode.getPath());
+                       int count = 0;
+                       Version version = null;
+                       for (VersionIterator it = history.getAllVersions(); it.hasNext();) {
+                               version = (Version) it.next();
+                               if (count == revision + 1) {
+                                       InputStream in = fromVersion(version);
+                                       if (log.isDebugEnabled())
+                                               log.debug("Retrieved " + path + " at revision "
+                                                               + revision);
+                                       return in;
+                               }
+                               count++;
+                       }
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot retrieve version " + revision
+                                       + " of " + path, e);
+               }
+
+               throw new ArgeoException("Version " + revision
+                               + " does not exist for node " + path);
+       }
+
+       protected InputStream fromVersion(Version version)
+                       throws RepositoryException {
+               Node frozenNode = version.getNode("jcr:frozenNode");
+               InputStream in = frozenNode.getNode(Property.JCR_CONTENT)
+                               .getProperty(Property.JCR_DATA).getBinary().getStream();
+               return in;
+       }
+
+       protected Session session() {
+               return session;
+       }
+
+       public void setVersioning(Boolean versioning) {
+               this.versioning = versioning;
+       }
+
+       public void setDefaultEncoding(String defaultEncoding) {
+               this.defaultEncoding = defaultEncoding;
+       }
+
+       protected String fill(Integer number) {
+               int size = 4;
+               String str = number.toString();
+               for (int i = str.length(); i < size; i++) {
+                       str = "0" + str;
+               }
+               return str;
+       }
+
+       public void setSession(Session session) {
+               this.session = session;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUrlStreamHandler.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUrlStreamHandler.java
new file mode 100644 (file)
index 0000000..a777639
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+import javax.jcr.Item;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
+public class JcrUrlStreamHandler extends URLStreamHandler {
+       private final Session session;
+
+       public JcrUrlStreamHandler(Session session) {
+               this.session = session;
+       }
+
+       @Override
+       protected URLConnection openConnection(final URL u) throws IOException {
+               // TODO Auto-generated method stub
+               return new URLConnection(u) {
+
+                       @Override
+                       public void connect() throws IOException {
+                               String itemPath = u.getPath();
+                               try {
+                                       if (!session.itemExists(itemPath))
+                                               throw new IOException("No item under " + itemPath);
+
+                                       Item item = session.getItem(u.getPath());
+                                       if (item.isNode()) {
+                                               // this should be a nt:file node
+                                               Node node = (Node) item;
+                                               if (!node.getPrimaryNodeType().isNodeType(
+                                                               NodeType.NT_FILE))
+                                                       throw new IOException("Node " + node + " is not a "
+                                                                       + NodeType.NT_FILE);
+
+                                       } else {
+                                               Property property = (Property) item;
+                                               if(property.getType()==PropertyType.BINARY){
+                                                       //Binary binary = property.getBinary();
+                                                       
+                                               }
+                                       }
+                               } catch (RepositoryException e) {
+                                       IOException ioe = new IOException(
+                                                       "Unexpected JCR exception");
+                                       ioe.initCause(e);
+                                       throw ioe;
+                               }
+                       }
+
+                       @Override
+                       public InputStream getInputStream() throws IOException {
+                               // TODO Auto-generated method stub
+                               return super.getInputStream();
+                       }
+
+               };
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUtils.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUtils.java
new file mode 100644 (file)
index 0000000..2176e75
--- /dev/null
@@ -0,0 +1,1590 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Principal;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Binary;
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.NoSuchWorkspaceException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.Workspace;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.observation.EventListener;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlList;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.AccessControlPolicyIterator;
+import javax.jcr.security.Privilege;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.ArgeoMonitor;
+import org.argeo.util.security.DigestUtils;
+import org.argeo.util.security.SimplePrincipal;
+
+/** Utility methods to simplify common JCR operations. */
+public class JcrUtils implements ArgeoJcrConstants {
+
+       final private static Log log = LogFactory.getLog(JcrUtils.class);
+
+       /**
+        * Not complete yet. See
+        * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
+        * %20Names
+        */
+       public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']',
+                       '|', '*', /*
+                                        * invalid XML chars :
+                                        */
+                       '<', '>', '&' };
+
+       /** Prevents instantiation */
+       private JcrUtils() {
+       }
+
+       /**
+        * Queries one single node.
+        * 
+        * @return one single node or null if none was found
+        * @throws ArgeoException
+        *             if more than one node was found
+        */
+       public static Node querySingleNode(Query query) {
+               NodeIterator nodeIterator;
+               try {
+                       QueryResult queryResult = query.execute();
+                       nodeIterator = queryResult.getNodes();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot execute query " + query, e);
+               }
+               Node node;
+               if (nodeIterator.hasNext())
+                       node = nodeIterator.nextNode();
+               else
+                       return null;
+
+               if (nodeIterator.hasNext())
+                       throw new ArgeoException("Query returned more than one node.");
+               return node;
+       }
+
+       /** Retrieves the node name from the provided path */
+       public static String nodeNameFromPath(String path) {
+               if (path.equals("/"))
+                       return "";
+               if (path.charAt(0) != '/')
+                       throw new ArgeoException("Path " + path + " must start with a '/'");
+               String pathT = path;
+               if (pathT.charAt(pathT.length() - 1) == '/')
+                       pathT = pathT.substring(0, pathT.length() - 2);
+
+               int index = pathT.lastIndexOf('/');
+               return pathT.substring(index + 1);
+       }
+
+       /** Retrieves the parent path of the provided path */
+       public static String parentPath(String path) {
+               if (path.equals("/"))
+                       throw new ArgeoException("Root path '/' has no parent path");
+               if (path.charAt(0) != '/')
+                       throw new ArgeoException("Path " + path + " must start with a '/'");
+               String pathT = path;
+               if (pathT.charAt(pathT.length() - 1) == '/')
+                       pathT = pathT.substring(0, pathT.length() - 2);
+
+               int index = pathT.lastIndexOf('/');
+               return pathT.substring(0, index);
+       }
+
+       /** The provided data as a path ('/' at the end, not the beginning) */
+       public static String dateAsPath(Calendar cal) {
+               return dateAsPath(cal, false);
+       }
+
+       /**
+        * Creates a deep path based on a URL:
+        * http://subdomain.example.com/to/content?args =>
+        * com/example/subdomain/to/content
+        */
+       public static String urlAsPath(String url) {
+               try {
+                       URL u = new URL(url);
+                       StringBuffer path = new StringBuffer(url.length());
+                       // invert host
+                       path.append(hostAsPath(u.getHost()));
+                       // we don't put port since it may not always be there and may change
+                       path.append(u.getPath());
+                       return path.toString();
+               } catch (MalformedURLException e) {
+                       throw new ArgeoException("Cannot generate URL path for " + url, e);
+               }
+       }
+
+       /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
+       public static void urlToAddressProperties(Node node, String url) {
+               try {
+                       URL u = new URL(url);
+                       node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
+                       node.setProperty(Property.JCR_HOST, u.getHost());
+                       node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
+                       node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot set URL " + url
+                                       + " as nt:address properties", e);
+               }
+       }
+
+       /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
+       public static String urlFromAddressProperties(Node node) {
+               try {
+                       URL u = new URL(
+                                       node.getProperty(Property.JCR_PROTOCOL).getString(), node
+                                                       .getProperty(Property.JCR_HOST).getString(),
+                                       (int) node.getProperty(Property.JCR_PORT).getLong(), node
+                                                       .getProperty(Property.JCR_PATH).getString());
+                       return u.toString();
+               } catch (Exception e) {
+                       throw new ArgeoException(
+                                       "Cannot get URL from nt:address properties of " + node, e);
+               }
+       }
+
+       /*
+        * PATH UTILITIES
+        */
+
+       /** Make sure that: starts with '/', do not end with '/', do not have '//' */
+       public static String normalizePath(String path) {
+               List<String> tokens = tokenize(path);
+               StringBuffer buf = new StringBuffer(path.length());
+               for (String token : tokens) {
+                       buf.append('/');
+                       buf.append(token);
+               }
+               return buf.toString();
+       }
+
+       /**
+        * Creates a path from a FQDN, inverting the order of the component:
+        * www.argeo.org => org.argeo.www
+        */
+       public static String hostAsPath(String host) {
+               StringBuffer path = new StringBuffer(host.length());
+               String[] hostTokens = host.split("\\.");
+               for (int i = hostTokens.length - 1; i >= 0; i--) {
+                       path.append(hostTokens[i]);
+                       if (i != 0)
+                               path.append('/');
+               }
+               return path.toString();
+       }
+
+       /**
+        * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c =>
+        * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
+        */
+       public static String uuidAsPath(String uuid) {
+               StringBuffer path = new StringBuffer(uuid.length());
+               String[] tokens = uuid.split("-");
+               for (int i = 0; i < tokens.length; i++) {
+                       path.append(tokens[i]);
+                       if (i != 0)
+                               path.append('/');
+               }
+               return path.toString();
+       }
+
+       /**
+        * The provided data as a path ('/' at the end, not the beginning)
+        * 
+        * @param cal
+        *            the date
+        * @param addHour
+        *            whether to add hour as well
+        */
+       public static String dateAsPath(Calendar cal, Boolean addHour) {
+               StringBuffer buf = new StringBuffer(14);
+               buf.append('Y');
+               buf.append(cal.get(Calendar.YEAR));
+               buf.append('/');
+
+               int month = cal.get(Calendar.MONTH) + 1;
+               buf.append('M');
+               if (month < 10)
+                       buf.append(0);
+               buf.append(month);
+               buf.append('/');
+
+               int day = cal.get(Calendar.DAY_OF_MONTH);
+               buf.append('D');
+               if (day < 10)
+                       buf.append(0);
+               buf.append(day);
+               buf.append('/');
+
+               if (addHour) {
+                       int hour = cal.get(Calendar.HOUR_OF_DAY);
+                       buf.append('H');
+                       if (hour < 10)
+                               buf.append(0);
+                       buf.append(hour);
+                       buf.append('/');
+               }
+               return buf.toString();
+
+       }
+
+       /** Converts in one call a string into a gregorian calendar. */
+       public static Calendar parseCalendar(DateFormat dateFormat, String value) {
+               try {
+                       Date date = dateFormat.parse(value);
+                       Calendar calendar = new GregorianCalendar();
+                       calendar.setTime(date);
+                       return calendar;
+               } catch (ParseException e) {
+                       throw new ArgeoException("Cannot parse " + value
+                                       + " with date format " + dateFormat, e);
+               }
+
+       }
+
+       /** The last element of a path. */
+       public static String lastPathElement(String path) {
+               if (path.charAt(path.length() - 1) == '/')
+                       throw new ArgeoException("Path " + path + " cannot end with '/'");
+               int index = path.lastIndexOf('/');
+               if (index < 0)
+                       return path;
+               return path.substring(index + 1);
+       }
+
+       /**
+        * Call {@link Node#getName()} without exceptions (useful in super
+        * constructors).
+        */
+       public static String getNameQuietly(Node node) {
+               try {
+                       return node.getName();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get name from " + node, e);
+               }
+       }
+
+       /**
+        * Call {@link Node#getProperty(String)} without exceptions (useful in super
+        * constructors).
+        */
+       public static String getStringPropertyQuietly(Node node, String propertyName) {
+               try {
+                       return node.getProperty(propertyName).getString();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get name from " + node, e);
+               }
+       }
+
+       /**
+        * Routine that get the child with this name, adding id it does not already
+        * exist
+        */
+       public static Node getOrAdd(Node parent, String childName,
+                       String childPrimaryNodeType) throws RepositoryException {
+               return parent.hasNode(childName) ? parent.getNode(childName) : parent
+                               .addNode(childName, childPrimaryNodeType);
+       }
+
+       /**
+        * Routine that get the child with this name, adding id it does not already
+        * exist
+        */
+       public static Node getOrAdd(Node parent, String childName)
+                       throws RepositoryException {
+               return parent.hasNode(childName) ? parent.getNode(childName) : parent
+                               .addNode(childName);
+       }
+
+       /** Convert a {@link NodeIterator} to a list of {@link Node} */
+       public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
+               List<Node> nodes = new ArrayList<Node>();
+               while (nodeIterator.hasNext()) {
+                       nodes.add(nodeIterator.nextNode());
+               }
+               return nodes;
+       }
+
+       /*
+        * PROPERTIES
+        */
+
+       /**
+        * Concisely get the string value of a property or null if this node doesn't
+        * have this property
+        */
+       public static String get(Node node, String propertyName) {
+               try {
+                       if (!node.hasProperty(propertyName))
+                               return null;
+                       return node.getProperty(propertyName).getString();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get property " + propertyName
+                                       + " of " + node, e);
+               }
+       }
+
+       /** Concisely get the boolean value of a property */
+       public static Boolean check(Node node, String propertyName) {
+               try {
+                       return node.getProperty(propertyName).getBoolean();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get property " + propertyName
+                                       + " of " + node, e);
+               }
+       }
+
+       /** Concisely get the bytes array value of a property */
+       public static byte[] getBytes(Node node, String propertyName) {
+               try {
+                       return getBinaryAsBytes(node.getProperty(propertyName));
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot get property " + propertyName
+                                       + " of " + node, e);
+               }
+       }
+
+       /** Creates the nodes making path, if they don't exist. */
+       public static Node mkdirs(Session session, String path) {
+               return mkdirs(session, path, null, null, false);
+       }
+
+       /**
+        * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
+        * 
+        * @deprecated
+        */
+       @Deprecated
+       public static Node mkdirs(Session session, String path, String type,
+                       Boolean versioning) {
+               return mkdirs(session, path, type, type, false);
+       }
+
+       /**
+        * @param type
+        *            the type of the leaf node
+        */
+       public static Node mkdirs(Session session, String path, String type) {
+               return mkdirs(session, path, type, null, false);
+       }
+
+       /**
+        * Create sub nodes relative to a parent node
+        * 
+        * @param nodeType
+        *            the type of the leaf node
+        */
+       public static Node mkdirs(Node parentNode, String relativePath,
+                       String nodeType) {
+               return mkdirs(parentNode, relativePath, nodeType, null);
+       }
+
+       /**
+        * Create sub nodes relative to a parent node
+        * 
+        * @param nodeType
+        *            the type of the leaf node
+        */
+       public static Node mkdirs(Node parentNode, String relativePath,
+                       String nodeType, String intermediaryNodeType) {
+               List<String> tokens = tokenize(relativePath);
+               Node currParent = parentNode;
+               try {
+                       for (int i = 0; i < tokens.size(); i++) {
+                               String name = tokens.get(i);
+                               if (currParent.hasNode(name)) {
+                                       currParent = currParent.getNode(name);
+                               } else {
+                                       if (i != (tokens.size() - 1)) {// intermediary
+                                               currParent = currParent.addNode(name,
+                                                               intermediaryNodeType);
+                                       } else {// leaf
+                                               currParent = currParent.addNode(name, nodeType);
+                                       }
+                               }
+                       }
+                       return currParent;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot mkdirs relative path "
+                                       + relativePath + " from " + parentNode, e);
+               }
+       }
+
+       /**
+        * Synchronized and save is performed, to avoid race conditions in
+        * initializers leading to duplicate nodes.
+        */
+       public synchronized static Node mkdirsSafe(Session session, String path,
+                       String type) {
+               try {
+                       if (session.hasPendingChanges())
+                               throw new ArgeoException(
+                                               "Session has pending changes, save them first.");
+                       Node node = mkdirs(session, path, type);
+                       session.save();
+                       return node;
+               } catch (RepositoryException e) {
+                       discardQuietly(session);
+                       throw new ArgeoException("Cannot safely make directories", e);
+               }
+       }
+
+       public synchronized static Node mkdirsSafe(Session session, String path) {
+               return mkdirsSafe(session, path, null);
+       }
+
+       /**
+        * Creates the nodes making path, if they don't exist. This is up to the
+        * caller to save the session. Use with caution since it can create
+        * duplicate nodes if used concurrently.
+        */
+       public static Node mkdirs(Session session, String path, String type,
+                       String intermediaryNodeType, Boolean versioning) {
+               try {
+                       if (path.equals('/'))
+                               return session.getRootNode();
+
+                       if (session.itemExists(path)) {
+                               Node node = session.getNode(path);
+                               // check type
+                               if (type != null && !node.isNodeType(type)
+                                               && !node.getPath().equals("/"))
+                                       throw new ArgeoException("Node " + node
+                                                       + " exists but is of type "
+                                                       + node.getPrimaryNodeType().getName()
+                                                       + " not of type " + type);
+                               // TODO: check versioning
+                               return node;
+                       }
+
+                       StringBuffer current = new StringBuffer("/");
+                       Node currentNode = session.getRootNode();
+                       Iterator<String> it = tokenize(path).iterator();
+                       while (it.hasNext()) {
+                               String part = it.next();
+                               current.append(part).append('/');
+                               if (!session.itemExists(current.toString())) {
+                                       if (!it.hasNext() && type != null)
+                                               currentNode = currentNode.addNode(part, type);
+                                       else if (it.hasNext() && intermediaryNodeType != null)
+                                               currentNode = currentNode.addNode(part,
+                                                               intermediaryNodeType);
+                                       else
+                                               currentNode = currentNode.addNode(part);
+                                       if (versioning)
+                                               currentNode.addMixin(NodeType.MIX_VERSIONABLE);
+                                       if (log.isTraceEnabled())
+                                               log.debug("Added folder " + part + " as " + current);
+                               } else {
+                                       currentNode = (Node) session.getItem(current.toString());
+                               }
+                       }
+                       return currentNode;
+               } catch (RepositoryException e) {
+                       discardQuietly(session);
+                       throw new ArgeoException("Cannot mkdirs " + path, e);
+               } finally {
+               }
+       }
+
+       /** Convert a path to the list of its tokens */
+       public static List<String> tokenize(String path) {
+               List<String> tokens = new ArrayList<String>();
+               boolean optimized = false;
+               if (!optimized) {
+                       String[] rawTokens = path.split("/");
+                       for (String token : rawTokens) {
+                               if (!token.equals(""))
+                                       tokens.add(token);
+                       }
+               } else {
+                       StringBuffer curr = new StringBuffer();
+                       char[] arr = path.toCharArray();
+                       chars: for (int i = 0; i < arr.length; i++) {
+                               char c = arr[i];
+                               if (c == '/') {
+                                       if (i == 0 || (i == arr.length - 1))
+                                               continue chars;
+                                       if (curr.length() > 0) {
+                                               tokens.add(curr.toString());
+                                               curr = new StringBuffer();
+                                       }
+                               } else
+                                       curr.append(c);
+                       }
+                       if (curr.length() > 0) {
+                               tokens.add(curr.toString());
+                               curr = new StringBuffer();
+                       }
+               }
+               return Collections.unmodifiableList(tokens);
+       }
+
+       /**
+        * Safe and repository implementation independent registration of a
+        * namespace.
+        */
+       public static void registerNamespaceSafely(Session session, String prefix,
+                       String uri) {
+               try {
+                       registerNamespaceSafely(session.getWorkspace()
+                                       .getNamespaceRegistry(), prefix, uri);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot find namespace registry", e);
+               }
+       }
+
+       /**
+        * Safe and repository implementation independent registration of a
+        * namespace.
+        */
+       public static void registerNamespaceSafely(NamespaceRegistry nr,
+                       String prefix, String uri) {
+               try {
+                       String[] prefixes = nr.getPrefixes();
+                       for (String pref : prefixes)
+                               if (pref.equals(prefix)) {
+                                       String registeredUri = nr.getURI(pref);
+                                       if (!registeredUri.equals(uri))
+                                               throw new ArgeoException("Prefix " + pref
+                                                               + " already registered for URI "
+                                                               + registeredUri
+                                                               + " which is different from provided URI "
+                                                               + uri);
+                                       else
+                                               return;// skip
+                               }
+                       nr.registerNamespace(prefix, uri);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot register namespace " + uri
+                                       + " under prefix " + prefix, e);
+               }
+       }
+
+       /** Recursively outputs the contents of the given node. */
+       public static void debug(Node node) {
+               debug(node, log);
+       }
+
+       /** Recursively outputs the contents of the given node. */
+       public static void debug(Node node, Log log) {
+               try {
+                       // First output the node path
+                       log.debug(node.getPath());
+                       // Skip the virtual (and large!) jcr:system subtree
+                       if (node.getName().equals("jcr:system")) {
+                               return;
+                       }
+
+                       // Then the children nodes (recursive)
+                       NodeIterator it = node.getNodes();
+                       while (it.hasNext()) {
+                               Node childNode = it.nextNode();
+                               debug(childNode, log);
+                       }
+
+                       // Then output the properties
+                       PropertyIterator properties = node.getProperties();
+                       // log.debug("Property are : ");
+
+                       properties: while (properties.hasNext()) {
+                               Property property = properties.nextProperty();
+                               if (property.getType() == PropertyType.BINARY)
+                                       continue properties;// skip
+                               if (property.getDefinition().isMultiple()) {
+                                       // A multi-valued property, print all values
+                                       Value[] values = property.getValues();
+                                       for (int i = 0; i < values.length; i++) {
+                                               log.debug(property.getPath() + "="
+                                                               + values[i].getString());
+                                       }
+                               } else {
+                                       // A single-valued property
+                                       log.debug(property.getPath() + "=" + property.getString());
+                               }
+                       }
+               } catch (Exception e) {
+                       log.error("Could not debug " + node, e);
+               }
+
+       }
+
+       /** Logs the effective access control policies */
+       public static void logEffectiveAccessPolicies(Node node) {
+               try {
+                       logEffectiveAccessPolicies(node.getSession(), node.getPath());
+               } catch (RepositoryException e) {
+                       log.error("Cannot log effective access policies of " + node, e);
+               }
+       }
+
+       /** Logs the effective access control policies */
+       public static void logEffectiveAccessPolicies(Session session, String path) {
+               if (!log.isDebugEnabled())
+                       return;
+
+               try {
+                       AccessControlPolicy[] effectivePolicies = session
+                                       .getAccessControlManager().getEffectivePolicies(path);
+                       if (effectivePolicies.length > 0) {
+                               for (AccessControlPolicy policy : effectivePolicies) {
+                                       if (policy instanceof AccessControlList) {
+                                               AccessControlList acl = (AccessControlList) policy;
+                                               log.debug("Access control list for " + path + "\n"
+                                                               + accessControlListSummary(acl));
+                                       }
+                               }
+                       } else {
+                               log.debug("No effective access control policy for " + path);
+                       }
+               } catch (RepositoryException e) {
+                       log.error("Cannot log effective access policies of " + path, e);
+               }
+       }
+
+       /** Returns a human-readable summary of this access control list. */
+       public static String accessControlListSummary(AccessControlList acl) {
+               StringBuffer buf = new StringBuffer("");
+               try {
+                       for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+                               buf.append('\t').append(ace.getPrincipal().getName())
+                                               .append('\n');
+                               for (Privilege priv : ace.getPrivileges())
+                                       buf.append("\t\t").append(priv.getName()).append('\n');
+                       }
+                       return buf.toString();
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot write summary of " + acl, e);
+               }
+       }
+
+       /**
+        * Copies recursively the content of a node to another one. Do NOT copy the
+        * property values of {@link NodeType#MIX_CREATED} and
+        * {@link NodeType#MIX_LAST_MODIFIED}, but update the
+        * {@link Property#JCR_LAST_MODIFIED} and
+        * {@link Property#JCR_LAST_MODIFIED_BY} properties if the target node has
+        * the {@link NodeType#MIX_LAST_MODIFIED} mixin.
+        */
+       public static void copy(Node fromNode, Node toNode) {
+               try {
+                       if (toNode.getDefinition().isProtected())
+                               return;
+
+                       // process properties
+                       PropertyIterator pit = fromNode.getProperties();
+                       properties: while (pit.hasNext()) {
+                               Property fromProperty = pit.nextProperty();
+                               String propertyName = fromProperty.getName();
+                               if (toNode.hasProperty(propertyName)
+                                               && toNode.getProperty(propertyName).getDefinition()
+                                                               .isProtected())
+                                       continue properties;
+
+                               if (fromProperty.getDefinition().isProtected())
+                                       continue properties;
+
+                               if (propertyName.equals("jcr:created")
+                                               || propertyName.equals("jcr:createdBy")
+                                               || propertyName.equals("jcr:lastModified")
+                                               || propertyName.equals("jcr:lastModifiedBy"))
+                                       continue properties;
+
+                               if (fromProperty.isMultiple()) {
+                                       toNode.setProperty(propertyName, fromProperty.getValues());
+                               } else {
+                                       toNode.setProperty(propertyName, fromProperty.getValue());
+                               }
+                       }
+
+                       // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
+                       // they existed, before adding the mixins
+                       updateLastModified(toNode);
+
+                       // add mixins
+                       for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
+                               toNode.addMixin(mixinType.getName());
+                       }
+
+                       // process children nodes
+                       NodeIterator nit = fromNode.getNodes();
+                       while (nit.hasNext()) {
+                               Node fromChild = nit.nextNode();
+                               Integer index = fromChild.getIndex();
+                               String nodeRelPath = fromChild.getName() + "[" + index + "]";
+                               Node toChild;
+                               if (toNode.hasNode(nodeRelPath))
+                                       toChild = toNode.getNode(nodeRelPath);
+                               else
+                                       toChild = toNode.addNode(fromChild.getName(), fromChild
+                                                       .getPrimaryNodeType().getName());
+                               copy(fromChild, toChild);
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot copy " + fromNode + " to "
+                                       + toNode, e);
+               }
+       }
+
+       /**
+        * Check whether all first-level properties (except jcr:* properties) are
+        * equal. Skip jcr:* properties
+        */
+       public static Boolean allPropertiesEquals(Node reference, Node observed,
+                       Boolean onlyCommonProperties) {
+               try {
+                       PropertyIterator pit = reference.getProperties();
+                       props: while (pit.hasNext()) {
+                               Property propReference = pit.nextProperty();
+                               String propName = propReference.getName();
+                               if (propName.startsWith("jcr:"))
+                                       continue props;
+
+                               if (!observed.hasProperty(propName))
+                                       if (onlyCommonProperties)
+                                               continue props;
+                                       else
+                                               return false;
+                               // TODO: deal with multiple property values?
+                               if (!observed.getProperty(propName).getValue()
+                                               .equals(propReference.getValue()))
+                                       return false;
+                       }
+                       return true;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot check all properties equals of "
+                                       + reference + " and " + observed, e);
+               }
+       }
+
+       public static Map<String, PropertyDiff> diffProperties(Node reference,
+                       Node observed) {
+               Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+               diffPropertiesLevel(diffs, null, reference, observed);
+               return diffs;
+       }
+
+       /**
+        * Compare the properties of two nodes. Recursivity to child nodes is not
+        * yet supported. Skip jcr:* properties.
+        */
+       static void diffPropertiesLevel(Map<String, PropertyDiff> diffs,
+                       String baseRelPath, Node reference, Node observed) {
+               try {
+                       // check removed and modified
+                       PropertyIterator pit = reference.getProperties();
+                       props: while (pit.hasNext()) {
+                               Property p = pit.nextProperty();
+                               String name = p.getName();
+                               if (name.startsWith("jcr:"))
+                                       continue props;
+
+                               if (!observed.hasProperty(name)) {
+                                       String relPath = propertyRelPath(baseRelPath, name);
+                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED,
+                                                       relPath, p.getValue(), null);
+                                       diffs.put(relPath, pDiff);
+                               } else {
+                                       if (p.isMultiple()) {
+                                               // FIXME implement multiple
+                                       } else {
+                                               Value referenceValue = p.getValue();
+                                               Value newValue = observed.getProperty(name).getValue();
+                                               if (!referenceValue.equals(newValue)) {
+                                                       String relPath = propertyRelPath(baseRelPath, name);
+                                                       PropertyDiff pDiff = new PropertyDiff(
+                                                                       PropertyDiff.MODIFIED, relPath,
+                                                                       referenceValue, newValue);
+                                                       diffs.put(relPath, pDiff);
+                                               }
+                                       }
+                               }
+                       }
+                       // check added
+                       pit = observed.getProperties();
+                       props: while (pit.hasNext()) {
+                               Property p = pit.nextProperty();
+                               String name = p.getName();
+                               if (name.startsWith("jcr:"))
+                                       continue props;
+                               if (!reference.hasProperty(name)) {
+                                       if (p.isMultiple()) {
+                                               // FIXME implement multiple
+                                       } else {
+                                               String relPath = propertyRelPath(baseRelPath, name);
+                                               PropertyDiff pDiff = new PropertyDiff(
+                                                               PropertyDiff.ADDED, relPath, null, p.getValue());
+                                               diffs.put(relPath, pDiff);
+                                       }
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot diff " + reference + " and "
+                                       + observed, e);
+               }
+       }
+
+       /**
+        * Compare only a restricted list of properties of two nodes. No
+        * recursivity.
+        * 
+        */
+       public static Map<String, PropertyDiff> diffProperties(Node reference,
+                       Node observed, List<String> properties) {
+               Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+               try {
+                       Iterator<String> pit = properties.iterator();
+
+                       props: while (pit.hasNext()) {
+                               String name = pit.next();
+                               if (!reference.hasProperty(name)) {
+                                       if (!observed.hasProperty(name))
+                                               continue props;
+                                       Value val = observed.getProperty(name).getValue();
+                                       try {
+                                               // empty String but not null
+                                               if ("".equals(val.getString()))
+                                                       continue props;
+                                       } catch (Exception e) {
+                                               // not parseable as String, silent
+                                       }
+                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED,
+                                                       name, null, val);
+                                       diffs.put(name, pDiff);
+                               } else if (!observed.hasProperty(name)) {
+                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED,
+                                                       name, reference.getProperty(name).getValue(), null);
+                                       diffs.put(name, pDiff);
+                               } else {
+                                       Value referenceValue = reference.getProperty(name)
+                                                       .getValue();
+                                       Value newValue = observed.getProperty(name).getValue();
+                                       if (!referenceValue.equals(newValue)) {
+                                               PropertyDiff pDiff = new PropertyDiff(
+                                                               PropertyDiff.MODIFIED, name, referenceValue,
+                                                               newValue);
+                                               diffs.put(name, pDiff);
+                                       }
+                               }
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot diff " + reference + " and "
+                                       + observed, e);
+               }
+               return diffs;
+       }
+
+       /** Builds a property relPath to be used in the diff. */
+       private static String propertyRelPath(String baseRelPath,
+                       String propertyName) {
+               if (baseRelPath == null)
+                       return propertyName;
+               else
+                       return baseRelPath + '/' + propertyName;
+       }
+
+       /**
+        * Normalizes a name so that it can be stored in contexts not supporting
+        * names with ':' (typically databases). Replaces ':' by '_'.
+        */
+       public static String normalize(String name) {
+               return name.replace(':', '_');
+       }
+
+       /**
+        * Replaces characters which are invalid in a JCR name by '_'. Currently not
+        * exhaustive.
+        * 
+        * @see JcrUtils#INVALID_NAME_CHARACTERS
+        */
+       public static String replaceInvalidChars(String name) {
+               return replaceInvalidChars(name, '_');
+       }
+
+       /**
+        * Replaces characters which are invalid in a JCR name. Currently not
+        * exhaustive.
+        * 
+        * @see JcrUtils#INVALID_NAME_CHARACTERS
+        */
+       public static String replaceInvalidChars(String name, char replacement) {
+               boolean modified = false;
+               char[] arr = name.toCharArray();
+               for (int i = 0; i < arr.length; i++) {
+                       char c = arr[i];
+                       invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
+                               if (c == invalid) {
+                                       arr[i] = replacement;
+                                       modified = true;
+                                       break invalid;
+                               }
+                       }
+               }
+               if (modified)
+                       return new String(arr);
+               else
+                       // do not create new object if unnecessary
+                       return name;
+       }
+
+       /**
+        * Removes forbidden characters from a path, replacing them with '_'
+        * 
+        * @deprecated use {@link #replaceInvalidChars(String)} instead
+        */
+       public static String removeForbiddenCharacters(String str) {
+               return str.replace('[', '_').replace(']', '_').replace('/', '_')
+                               .replace('*', '_');
+
+       }
+
+       /** Cleanly disposes a {@link Binary} even if it is null. */
+       public static void closeQuietly(Binary binary) {
+               if (binary == null)
+                       return;
+               binary.dispose();
+       }
+
+       /** Retrieve a {@link Binary} as a byte array */
+       public static byte[] getBinaryAsBytes(Property property) {
+               ByteArrayOutputStream out = new ByteArrayOutputStream();
+               InputStream in = null;
+               Binary binary = null;
+               try {
+                       binary = property.getBinary();
+                       in = binary.getStream();
+                       IOUtils.copy(in, out);
+                       return out.toByteArray();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot read binary " + property
+                                       + " as bytes", e);
+               } finally {
+                       IOUtils.closeQuietly(out);
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
+       /** Writes a {@link Binary} from a byte array */
+       public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
+               InputStream in = null;
+               Binary binary = null;
+               try {
+                       in = new ByteArrayInputStream(bytes);
+                       binary = node.getSession().getValueFactory().createBinary(in);
+                       node.setProperty(property, binary);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot read binary " + property
+                                       + " as bytes", e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
+       /**
+        * Creates depth from a string (typically a username) by adding levels based
+        * on its first characters: "aBcD",2 => a/aB
+        */
+       public static String firstCharsToPath(String str, Integer nbrOfChars) {
+               if (str.length() < nbrOfChars)
+                       throw new ArgeoException("String " + str
+                                       + " length must be greater or equal than " + nbrOfChars);
+               StringBuffer path = new StringBuffer("");
+               StringBuffer curr = new StringBuffer("");
+               for (int i = 0; i < nbrOfChars; i++) {
+                       curr.append(str.charAt(i));
+                       path.append(curr);
+                       if (i < nbrOfChars - 1)
+                               path.append('/');
+               }
+               return path.toString();
+       }
+
+       /**
+        * Discards the current changes in the session attached to this node. To be
+        * used typically in a catch block.
+        * 
+        * @see #discardQuietly(Session)
+        */
+       public static void discardUnderlyingSessionQuietly(Node node) {
+               try {
+                       discardQuietly(node.getSession());
+               } catch (RepositoryException e) {
+                       log.warn("Cannot quietly discard session of node " + node + ": "
+                                       + e.getMessage());
+               }
+       }
+
+       /**
+        * Discards the current changes in a session by calling
+        * {@link Session#refresh(boolean)} with <code>false</code>, only logging
+        * potential errors when doing so. To be used typically in a catch block.
+        */
+       public static void discardQuietly(Session session) {
+               try {
+                       if (session != null)
+                               session.refresh(false);
+               } catch (RepositoryException e) {
+                       log.warn("Cannot quietly discard session " + session + ": "
+                                       + e.getMessage());
+               }
+       }
+
+       /**
+        * Login to a workspace with implicit credentials, creates the workspace
+        * with these credentials if it does not already exist.
+        */
+       public static Session loginOrCreateWorkspace(Repository repository,
+                       String workspaceName) throws RepositoryException {
+               Session workspaceSession = null;
+               Session defaultSession = null;
+               try {
+                       try {
+                               workspaceSession = repository.login(workspaceName);
+                       } catch (NoSuchWorkspaceException e) {
+                               // try to create workspace
+                               defaultSession = repository.login();
+                               defaultSession.getWorkspace().createWorkspace(workspaceName);
+                               workspaceSession = repository.login(workspaceName);
+                       }
+                       return workspaceSession;
+               } finally {
+                       logoutQuietly(defaultSession);
+               }
+       }
+
+       /** Logs out the session, not throwing any exception, even if it is null. */
+       public static void logoutQuietly(Session session) {
+               try {
+                       if (session != null)
+                               if (session.isLive())
+                                       session.logout();
+               } catch (Exception e) {
+                       // silent
+               }
+       }
+
+       /**
+        * Convenient method to add a listener. uuids passed as null, deep=true,
+        * local=true, only one node type
+        */
+       public static void addListener(Session session, EventListener listener,
+                       int eventTypes, String basePath, String nodeType) {
+               try {
+                       session.getWorkspace()
+                                       .getObservationManager()
+                                       .addEventListener(
+                                                       listener,
+                                                       eventTypes,
+                                                       basePath,
+                                                       true,
+                                                       null,
+                                                       nodeType == null ? null : new String[] { nodeType },
+                                                       true);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot add JCR listener " + listener
+                                       + " to session " + session, e);
+               }
+       }
+
+       /** Removes a listener without throwing exception */
+       public static void removeListenerQuietly(Session session,
+                       EventListener listener) {
+               if (session == null || !session.isLive())
+                       return;
+               try {
+                       session.getWorkspace().getObservationManager()
+                                       .removeEventListener(listener);
+               } catch (RepositoryException e) {
+                       // silent
+               }
+       }
+
+       /**
+        * Quietly unregisters an {@link EventListener} from the udnerlying
+        * workspace of this node.
+        */
+       public static void unregisterQuietly(Node node, EventListener eventListener) {
+               try {
+                       unregisterQuietly(node.getSession().getWorkspace(), eventListener);
+               } catch (RepositoryException e) {
+                       // silent
+                       if (log.isTraceEnabled())
+                               log.trace("Could not unregister event listener "
+                                               + eventListener);
+               }
+       }
+
+       /** Quietly unregisters an {@link EventListener} from this workspace */
+       public static void unregisterQuietly(Workspace workspace,
+                       EventListener eventListener) {
+               if (eventListener == null)
+                       return;
+               try {
+                       workspace.getObservationManager()
+                                       .removeEventListener(eventListener);
+               } catch (RepositoryException e) {
+                       // silent
+                       if (log.isTraceEnabled())
+                               log.trace("Could not unregister event listener "
+                                               + eventListener);
+               }
+       }
+
+       /**
+        * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it
+        * updates the {@link Property#JCR_LAST_MODIFIED} property with the current
+        * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the
+        * underlying session user id. In Jackrabbit 2.x, <a
+        * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
+        * are not automatically updated</a>, hence the need for manual update. The
+        * session is not saved.
+        */
+       public static void updateLastModified(Node node) {
+               try {
+                       if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
+                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
+                       node.setProperty(Property.JCR_LAST_MODIFIED,
+                                       new GregorianCalendar());
+                       node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession()
+                                       .getUserID());
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot update last modified on " + node,
+                                       e);
+               }
+       }
+
+       /**
+        * Update lastModified recursively until this parent.
+        * 
+        * @param node
+        *            the node
+        * @param untilPath
+        *            the base path, null is equivalent to "/"
+        */
+       public static void updateLastModifiedAndParents(Node node, String untilPath) {
+               try {
+                       if (untilPath != null && !node.getPath().startsWith(untilPath))
+                               throw new ArgeoException(node + " is not under " + untilPath);
+                       updateLastModified(node);
+                       if (untilPath == null) {
+                               if (!node.getPath().equals("/"))
+                                       updateLastModifiedAndParents(node.getParent(), untilPath);
+                       } else {
+                               if (!node.getPath().equals(untilPath))
+                                       updateLastModifiedAndParents(node.getParent(), untilPath);
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot update lastModified from " + node
+                                       + " until " + untilPath, e);
+               }
+       }
+
+       /**
+        * Returns a String representing the short version (see <a
+        * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+        * Notation </a> attributes grammar) of the main business attributes of this
+        * property definition
+        * 
+        * @param prop
+        */
+       public static String getPropertyDefinitionAsString(Property prop) {
+               StringBuffer sbuf = new StringBuffer();
+               try {
+                       if (prop.getDefinition().isAutoCreated())
+                               sbuf.append("a");
+                       if (prop.getDefinition().isMandatory())
+                               sbuf.append("m");
+                       if (prop.getDefinition().isProtected())
+                               sbuf.append("p");
+                       if (prop.getDefinition().isMultiple())
+                               sbuf.append("*");
+               } catch (RepositoryException re) {
+                       throw new ArgeoException(
+                                       "unexpected error while getting property definition as String",
+                                       re);
+               }
+               return sbuf.toString();
+       }
+
+       /**
+        * Estimate the sub tree size from current node. Computation is based on the
+        * Jcr {@link Property.getLength()} method. Note : it is not the exact size
+        * used on the disk by the current part of the JCR Tree.
+        */
+
+       public static long getNodeApproxSize(Node node) {
+               long curNodeSize = 0;
+               try {
+                       PropertyIterator pi = node.getProperties();
+                       while (pi.hasNext()) {
+                               Property prop = pi.nextProperty();
+                               if (prop.isMultiple()) {
+                                       int nb = prop.getLengths().length;
+                                       for (int i = 0; i < nb; i++) {
+                                               curNodeSize += (prop.getLengths()[i] > 0 ? prop
+                                                               .getLengths()[i] : 0);
+                                       }
+                               } else
+                                       curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
+                       }
+
+                       NodeIterator ni = node.getNodes();
+                       while (ni.hasNext())
+                               curNodeSize += getNodeApproxSize(ni.nextNode());
+                       return curNodeSize;
+               } catch (RepositoryException re) {
+                       throw new ArgeoException(
+                                       "Unexpected error while recursively determining node size.",
+                                       re);
+               }
+       }
+
+       /*
+        * SECURITY
+        */
+
+       /**
+        * Convenience method for adding a single privilege to a principal (user or
+        * role), typically jcr:all
+        */
+       public synchronized static void addPrivilege(Session session, String path,
+                       String principal, String privilege) throws RepositoryException {
+               List<Privilege> privileges = new ArrayList<Privilege>();
+               privileges.add(session.getAccessControlManager().privilegeFromName(
+                               privilege));
+               addPrivileges(session, path, new SimplePrincipal(principal), privileges);
+       }
+
+       /**
+        * Add privileges on a path to a {@link Principal}. The path must already
+        * exist. Session is saved. Synchronized to prevent concurrent modifications
+        * of the same node.
+        */
+       public synchronized static Boolean addPrivileges(Session session,
+                       String path, Principal principal, List<Privilege> privs)
+                       throws RepositoryException {
+               // make sure the session is in line with the persisted state
+               session.refresh(false);
+               AccessControlManager acm = session.getAccessControlManager();
+               AccessControlList acl = getAccessControlList(acm, path);
+
+               accessControlEntries: for (AccessControlEntry ace : acl
+                               .getAccessControlEntries()) {
+                       Principal currentPrincipal = ace.getPrincipal();
+                       if (currentPrincipal.getName().equals(principal.getName())) {
+                               Privilege[] currentPrivileges = ace.getPrivileges();
+                               if (currentPrivileges.length != privs.size())
+                                       break accessControlEntries;
+                               for (int i = 0; i < currentPrivileges.length; i++) {
+                                       Privilege currP = currentPrivileges[i];
+                                       Privilege p = privs.get(i);
+                                       if (!currP.getName().equals(p.getName())) {
+                                               break accessControlEntries;
+                                       }
+                               }
+                               return false;
+                       }
+               }
+
+               Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
+               acl.addAccessControlEntry(principal, privileges);
+               acm.setPolicy(path, acl);
+               if (log.isDebugEnabled()) {
+                       StringBuffer privBuf = new StringBuffer();
+                       for (Privilege priv : privs)
+                               privBuf.append(priv.getName());
+                       log.debug("Added privileges " + privBuf + " to "
+                                       + principal.getName() + " on " + path + " in '"
+                                       + session.getWorkspace().getName() + "'");
+               }
+               session.refresh(true);
+               session.save();
+               return true;
+       }
+
+       /** Gets access control list for this path, throws exception if not found */
+       public synchronized static AccessControlList getAccessControlList(
+                       AccessControlManager acm, String path) throws RepositoryException {
+               // search for an access control list
+               AccessControlList acl = null;
+               AccessControlPolicyIterator policyIterator = acm
+                               .getApplicablePolicies(path);
+               if (policyIterator.hasNext()) {
+                       while (policyIterator.hasNext()) {
+                               AccessControlPolicy acp = policyIterator
+                                               .nextAccessControlPolicy();
+                               if (acp instanceof AccessControlList)
+                                       acl = ((AccessControlList) acp);
+                       }
+               } else {
+                       AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+                       for (AccessControlPolicy acp : existingPolicies) {
+                               if (acp instanceof AccessControlList)
+                                       acl = ((AccessControlList) acp);
+                       }
+               }
+               if (acl != null)
+                       return acl;
+               else
+                       throw new ArgeoException("ACL not found at " + path);
+       }
+
+       /** Clear authorizations for a user at this path */
+       public synchronized static void clearAccessControList(Session session,
+                       String path, String username) throws RepositoryException {
+               AccessControlManager acm = session.getAccessControlManager();
+               AccessControlList acl = getAccessControlList(acm, path);
+               for (AccessControlEntry ace : acl.getAccessControlEntries()) {
+                       if (ace.getPrincipal().getName().equals(username)) {
+                               acl.removeAccessControlEntry(ace);
+                       }
+               }
+       }
+
+       /*
+        * FILES UTILITIES
+        */
+       /**
+        * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
+        */
+       public static Node mkfolders(Session session, String path) {
+               return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER,
+                               false);
+       }
+
+       /**
+        * Copy only nt:folder and nt:file, without their additional types and
+        * properties.
+        * 
+        * @param recursive
+        *            if true copies folders as well, otherwise only first level
+        *            files
+        * @return how many files were copied
+        */
+       @SuppressWarnings("resource")
+       public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive,
+                       ArgeoMonitor monitor) {
+               long count = 0l;
+
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       NodeIterator fromChildren = fromNode.getNodes();
+                       while (fromChildren.hasNext()) {
+                               if (monitor != null && monitor.isCanceled())
+                                       throw new ArgeoException(
+                                                       "Copy cancelled before it was completed");
+
+                               Node fromChild = fromChildren.nextNode();
+                               String fileName = fromChild.getName();
+                               if (fromChild.isNodeType(NodeType.NT_FILE)) {
+                                       if (monitor != null)
+                                               monitor.subTask("Copy " + fileName);
+                                       binary = fromChild.getNode(Node.JCR_CONTENT)
+                                                       .getProperty(Property.JCR_DATA).getBinary();
+                                       in = binary.getStream();
+                                       copyStreamAsFile(toNode, fileName, in);
+                                       IOUtils.closeQuietly(in);
+                                       closeQuietly(binary);
+
+                                       // save session
+                                       toNode.getSession().save();
+                                       count++;
+
+                                       if (log.isDebugEnabled())
+                                               log.debug("Copied file " + fromChild.getPath());
+                                       if (monitor != null)
+                                               monitor.worked(1);
+                               } else if (fromChild.isNodeType(NodeType.NT_FOLDER)
+                                               && recursive) {
+                                       Node toChildFolder;
+                                       if (toNode.hasNode(fileName)) {
+                                               toChildFolder = toNode.getNode(fileName);
+                                               if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
+                                                       throw new ArgeoException(toChildFolder
+                                                                       + " is not of type nt:folder");
+                                       } else {
+                                               toChildFolder = toNode.addNode(fileName,
+                                                               NodeType.NT_FOLDER);
+
+                                               // save session
+                                               toNode.getSession().save();
+                                       }
+                                       count = count
+                                                       + copyFiles(fromChild, toChildFolder, recursive,
+                                                                       monitor);
+                               }
+                       }
+                       return count;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot copy files between " + fromNode
+                                       + " and " + toNode);
+               } finally {
+                       // in case there was an exception
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(binary);
+               }
+       }
+
+       /**
+        * Iteratively count all file nodes in subtree, inefficient but can be
+        * useful when query are poorly supported, such as in remoting.
+        */
+       public static Long countFiles(Node node) {
+               Long localCount = 0l;
+               try {
+                       for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
+                               Node child = nit.nextNode();
+                               if (child.isNodeType(NodeType.NT_FOLDER))
+                                       localCount = localCount + countFiles(child);
+                               else if (child.isNodeType(NodeType.NT_FILE))
+                                       localCount = localCount + 1;
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot count all children of " + node);
+               }
+               return localCount;
+       }
+
+       /**
+        * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session
+        * is NOT saved.
+        * 
+        * @return the created file node
+        */
+       public static Node copyFile(Node folderNode, File file) {
+               InputStream in = null;
+               try {
+                       in = new FileInputStream(file);
+                       return copyStreamAsFile(folderNode, file.getName(), in);
+               } catch (IOException e) {
+                       throw new ArgeoException("Cannot copy file " + file + " under "
+                                       + folderNode, e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+               }
+       }
+
+       /** Copy bytes as an nt:file */
+       public static Node copyBytesAsFile(Node folderNode, String fileName,
+                       byte[] bytes) {
+               InputStream in = null;
+               try {
+                       in = new ByteArrayInputStream(bytes);
+                       return copyStreamAsFile(folderNode, fileName, in);
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot copy file " + fileName + " under "
+                                       + folderNode, e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+               }
+       }
+
+       /**
+        * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session
+        * is NOT saved.
+        * 
+        * @return the created file node
+        */
+       public static Node copyStreamAsFile(Node folderNode, String fileName,
+                       InputStream in) {
+               Binary binary = null;
+               try {
+                       Node fileNode;
+                       Node contentNode;
+                       if (folderNode.hasNode(fileName)) {
+                               fileNode = folderNode.getNode(fileName);
+                               if (!fileNode.isNodeType(NodeType.NT_FILE))
+                                       throw new ArgeoException(fileNode
+                                                       + " is not of type nt:file");
+                               // we assume that the content node is already there
+                               contentNode = fileNode.getNode(Node.JCR_CONTENT);
+                       } else {
+                               fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
+                               contentNode = fileNode.addNode(Node.JCR_CONTENT,
+                                               NodeType.NT_RESOURCE);
+                       }
+                       binary = contentNode.getSession().getValueFactory()
+                                       .createBinary(in);
+                       contentNode.setProperty(Property.JCR_DATA, binary);
+                       return fileNode;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot create file node " + fileName
+                                       + " under " + folderNode, e);
+               } finally {
+                       closeQuietly(binary);
+               }
+       }
+
+       /** Computes the checksum of an nt:file */
+       public static String checksumFile(Node fileNode, String algorithm) {
+               Binary data = null;
+               InputStream in = null;
+               try {
+                       data = fileNode.getNode(Node.JCR_CONTENT)
+                                       .getProperty(Property.JCR_DATA).getBinary();
+                       in = data.getStream();
+                       return DigestUtils.digest(algorithm, in);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot checksum file " + fileNode, e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       closeQuietly(data);
+               }
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/MaintainedRepository.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/MaintainedRepository.java
new file mode 100644 (file)
index 0000000..702d47a
--- /dev/null
@@ -0,0 +1,8 @@
+package org.argeo.jcr;
+
+import javax.jcr.Repository;
+
+/** Abstracts maintenance operations on a {@link Repository} */
+public interface MaintainedRepository extends Repository {
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapper.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapper.java
new file mode 100644 (file)
index 0000000..af792c3
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.Session;
+
+public interface NodeMapper {
+       public Object load(Node node);
+
+       public void update(Node node, Object obj);
+
+       public Node save(Session session, String path, Object obj);
+       
+       public void setNodeMapperProvider(NodeMapperProvider nmp);
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapperProvider.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapperProvider.java
new file mode 100644 (file)
index 0000000..07e623b
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Node;
+
+/** Provides a node mapper relevant for this node. */
+public interface NodeMapperProvider {
+
+       /** 
+        * Node Mapper is chosen regarding the Jcr path of the node parameter 
+        * @param Node node
+        * @return the node mapper or null if no relevant node mapper can be found. */
+       public NodeMapper findNodeMapper(Node node);
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/PropertyDiff.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/PropertyDiff.java
new file mode 100644 (file)
index 0000000..bdd316f
--- /dev/null
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Value;
+
+import org.argeo.ArgeoException;
+
+/** The result of the comparison of two JCR properties. */
+public class PropertyDiff {
+       public final static Integer MODIFIED = 0;
+       public final static Integer ADDED = 1;
+       public final static Integer REMOVED = 2;
+
+       private final Integer type;
+       private final String relPath;
+       private final Value referenceValue;
+       private final Value newValue;
+
+       public PropertyDiff(Integer type, String relPath, Value referenceValue,
+                       Value newValue) {
+               super();
+
+               if (type == MODIFIED) {
+                       if (referenceValue == null || newValue == null)
+                               throw new ArgeoException(
+                                               "Reference and new values must be specified.");
+               } else if (type == ADDED) {
+                       if (referenceValue != null || newValue == null)
+                               throw new ArgeoException(
+                                               "New value and only it must be specified.");
+               } else if (type == REMOVED) {
+                       if (referenceValue == null || newValue != null)
+                               throw new ArgeoException(
+                                               "Reference value and only it must be specified.");
+               } else {
+                       throw new ArgeoException("Unkown diff type " + type);
+               }
+
+               if (relPath == null)
+                       throw new ArgeoException("Relative path must be specified");
+
+               this.type = type;
+               this.relPath = relPath;
+               this.referenceValue = referenceValue;
+               this.newValue = newValue;
+       }
+
+       public Integer getType() {
+               return type;
+       }
+
+       public String getRelPath() {
+               return relPath;
+       }
+
+       public Value getReferenceValue() {
+               return referenceValue;
+       }
+
+       public Value getNewValue() {
+               return newValue;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/RepositoryRegister.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/RepositoryRegister.java
new file mode 100644 (file)
index 0000000..2e3d455
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryFactory;
+
+/** Allows to register repositories by name. */
+public interface RepositoryRegister extends RepositoryFactory {
+       /**
+        * The registered {@link Repository} as a read-only map. Note that this
+        * method should be called for each access in order to be sure to be up to
+        * date in case repositories have registered/unregistered
+        */
+       public Map<String, Repository> getRepositories();
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ThreadBoundJcrSessionFactory.java
new file mode 100644 (file)
index 0000000..193f22c
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.LoginException;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+
+/** Proxy JCR sessions and attach them to calling threads. */
+public abstract class ThreadBoundJcrSessionFactory {
+       private final static Log log = LogFactory
+                       .getLog(ThreadBoundJcrSessionFactory.class);
+
+       private Repository repository;
+       /** can be injected as list, only used if repository is null */
+       private List<Repository> repositories;
+
+       private ThreadLocal<Session> session = new ThreadLocal<Session>();
+       private final Session proxiedSession;
+       /** If workspace is null, default will be used. */
+       private String workspace = null;
+
+       private String defaultUsername = "demo";
+       private String defaultPassword = "demo";
+       private Boolean forceDefaultCredentials = false;
+
+       private boolean active = true;
+
+       // monitoring
+       private final List<Thread> threads = Collections
+                       .synchronizedList(new ArrayList<Thread>());
+       private final Map<Long, Session> activeSessions = Collections
+                       .synchronizedMap(new HashMap<Long, Session>());
+       private MonitoringThread monitoringThread;
+
+       public ThreadBoundJcrSessionFactory() {
+               Class<?>[] interfaces = { Session.class };
+               proxiedSession = (Session) Proxy.newProxyInstance(
+                               ThreadBoundJcrSessionFactory.class.getClassLoader(),
+                               interfaces, new JcrSessionInvocationHandler());
+       }
+
+       /** Logs in to the repository using various strategies. */
+       protected synchronized Session login() {
+               if (!isActive())
+                       throw new ArgeoException("Thread bound session factory inactive");
+
+               // discard session previously attached to this thread
+               Thread thread = Thread.currentThread();
+               if (activeSessions.containsKey(thread.getId())) {
+                       Session oldSession = activeSessions.remove(thread.getId());
+                       oldSession.logout();
+                       session.remove();
+               }
+
+               Session newSession = null;
+               // first try to login without credentials, assuming the underlying login
+               // module will have dealt with authentication (typically using Spring
+               // Security)
+               if (!forceDefaultCredentials)
+                       try {
+                               newSession = repository().login(workspace);
+                       } catch (LoginException e1) {
+                               log.warn("Cannot login without credentials: " + e1.getMessage());
+                               // invalid credentials, go to the next step
+                       } catch (RepositoryException e1) {
+                               // other kind of exception, fail
+                               throw new ArgeoException("Cannot log in to repository", e1);
+                       }
+
+               // log using default username / password (useful for testing purposes)
+               if (newSession == null)
+                       try {
+                               SimpleCredentials sc = new SimpleCredentials(defaultUsername,
+                                               defaultPassword.toCharArray());
+                               newSession = repository().login(sc, workspace);
+                       } catch (RepositoryException e) {
+                               throw new ArgeoException("Cannot log in to repository", e);
+                       }
+
+               session.set(newSession);
+               // Log and monitor new session
+               if (log.isTraceEnabled())
+                       log.trace("Logged in to JCR session " + newSession + "; userId="
+                                       + newSession.getUserID());
+
+               // monitoring
+               activeSessions.put(thread.getId(), newSession);
+               threads.add(thread);
+               return newSession;
+       }
+
+       public Object getObject() {
+               return proxiedSession;
+       }
+
+       public void init() throws Exception {
+               monitoringThread = new MonitoringThread();
+               monitoringThread.start();
+       }
+
+       public synchronized void dispose() throws Exception {
+               if (activeSessions.size() == 0)
+                       return;
+
+               if (log.isTraceEnabled())
+                       log.trace("Cleaning up " + activeSessions.size()
+                                       + " active JCR sessions...");
+
+               deactivate();
+               for (Session sess : activeSessions.values()) {
+                       JcrUtils.logoutQuietly(sess);
+               }
+               activeSessions.clear();
+       }
+
+       protected Boolean isActive() {
+               return active;
+       }
+
+       protected synchronized void deactivate() {
+               active = false;
+               notifyAll();
+       }
+
+       protected synchronized void removeSession(Thread thread) {
+               if (!isActive())
+                       return;
+               activeSessions.remove(thread.getId());
+               threads.remove(thread);
+       }
+
+       protected synchronized void cleanDeadThreads() {
+               if (!isActive())
+                       return;
+               Iterator<Thread> it = threads.iterator();
+               while (it.hasNext()) {
+                       Thread thread = it.next();
+                       if (!thread.isAlive() && isActive()) {
+                               if (activeSessions.containsKey(thread.getId())) {
+                                       Session session = activeSessions.get(thread.getId());
+                                       activeSessions.remove(thread.getId());
+                                       session.logout();
+                                       if (log.isTraceEnabled())
+                                               log.trace("Cleaned up JCR session (userID="
+                                                               + session.getUserID() + ") from dead thread "
+                                                               + thread.getId());
+                               }
+                               it.remove();
+                       }
+               }
+               try {
+                       wait(1000);
+               } catch (InterruptedException e) {
+                       // silent
+               }
+       }
+
+       public Class<? extends Session> getObjectType() {
+               return Session.class;
+       }
+
+       public boolean isSingleton() {
+               return true;
+       }
+
+       /**
+        * Called before a method is actually called, allowing to check the session
+        * or re-login it (e.g. if authentication has changed). The default
+        * implementation returns the session.
+        */
+       protected Session preCall(Session session) {
+               return session;
+       }
+
+       protected Repository repository() {
+               if (repository != null)
+                       return repository;
+               if (repositories != null) {
+                       // hardened for OSGi dynamic services
+                       Iterator<Repository> it = repositories.iterator();
+                       if (it.hasNext())
+                               return it.next();
+               }
+               throw new ArgeoException("No repository injected");
+       }
+
+       // /** Useful for declarative registration of OSGi services (blueprint) */
+       // public void register(Repository repository, Map<?, ?> params) {
+       // this.repository = repository;
+       // }
+       //
+       // /** Useful for declarative registration of OSGi services (blueprint) */
+       // public void unregister(Repository repository, Map<?, ?> params) {
+       // this.repository = null;
+       // }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public void setRepositories(List<Repository> repositories) {
+               this.repositories = repositories;
+       }
+
+       public void setDefaultUsername(String defaultUsername) {
+               this.defaultUsername = defaultUsername;
+       }
+
+       public void setDefaultPassword(String defaultPassword) {
+               this.defaultPassword = defaultPassword;
+       }
+
+       public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
+               this.forceDefaultCredentials = forceDefaultCredentials;
+       }
+
+       public void setWorkspace(String workspace) {
+               this.workspace = workspace;
+       }
+
+       protected class JcrSessionInvocationHandler implements InvocationHandler {
+
+               public Object invoke(Object proxy, Method method, Object[] args)
+                               throws Throwable, RepositoryException {
+                       Session threadSession = session.get();
+                       if (threadSession == null) {
+                               if ("logout".equals(method.getName()))// no need to login
+                                       return Void.TYPE;
+                               else if ("toString".equals(method.getName()))// maybe logging
+                                       return "Uninitialized Argeo thread bound JCR session";
+                               threadSession = login();
+                       }
+
+                       preCall(threadSession);
+                       Object ret;
+                       try {
+                               ret = method.invoke(threadSession, args);
+                       } catch (InvocationTargetException e) {
+                               Throwable cause = e.getCause();
+                               if (cause instanceof RepositoryException)
+                                       throw (RepositoryException) cause;
+                               else
+                                       throw cause;
+                       }
+                       if ("logout".equals(method.getName())) {
+                               session.remove();
+                               Thread thread = Thread.currentThread();
+                               removeSession(thread);
+                               if (log.isTraceEnabled())
+                                       log.trace("Logged out JCR session (userId="
+                                                       + threadSession.getUserID() + ") on thread "
+                                                       + thread.getId());
+                       }
+                       return ret;
+               }
+       }
+
+       /** Monitors registered thread in order to clean up dead ones. */
+       private class MonitoringThread extends Thread {
+
+               public MonitoringThread() {
+                       super("ThreadBound JCR Session Monitor");
+               }
+
+               @Override
+               public void run() {
+                       while (isActive()) {
+                               cleanDeadThreads();
+                       }
+               }
+
+       }
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/UserJcrUtils.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/UserJcrUtils.java
new file mode 100644 (file)
index 0000000..1758802
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.qom.Constraint;
+import javax.jcr.query.qom.DynamicOperand;
+import javax.jcr.query.qom.QueryObjectModelFactory;
+import javax.jcr.query.qom.Selector;
+import javax.jcr.query.qom.StaticOperand;
+
+import org.argeo.ArgeoException;
+
+/** Utilities related to the user home and properties based on Argeo JCR model. */
+public class UserJcrUtils {
+       /** The home base path. Not yet configurable */
+       public final static String DEFAULT_HOME_BASE_PATH = "/home";
+
+       /**
+        * Returns the home node of the user or null if none was found.
+        * 
+        * @param session
+        *            the session to use in order to perform the search, this can be
+        *            a session with a different user ID than the one searched,
+        *            typically when a system or admin session is used.
+        * @param username
+        *            the username of the user
+        */
+       public static Node getUserHome(Session session, String username) {
+               try {
+                       // String homePath = UserJcrUtils.getUserHomePath(username);
+                       // return session.itemExists(homePath) ? session.getNode(homePath)
+                       // : null;
+                       // kept for example of QOM queries
+                       QueryObjectModelFactory qomf = session.getWorkspace()
+                                       .getQueryManager().getQOMFactory();
+                       Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME,
+                                       "userHome");
+                       DynamicOperand userIdDop = qomf.propertyValue(
+                                       userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID);
+                       StaticOperand userIdSop = qomf.literal(session.getValueFactory()
+                                       .createValue(username));
+                       Constraint constraint = qomf.comparison(userIdDop,
+                                       QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop);
+                       Query query = qomf.createQuery(userHomeSel, constraint, null, null);
+                       return JcrUtils.querySingleNode(query);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot find home for user " + username, e);
+               }
+       }
+
+       public static Node getUserProfile(Session session, String username) {
+               try {
+                       QueryObjectModelFactory qomf = session.getWorkspace()
+                                       .getQueryManager().getQOMFactory();
+                       Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE,
+                                       "userProfile");
+                       DynamicOperand userIdDop = qomf.propertyValue(
+                                       userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID);
+                       StaticOperand userIdSop = qomf.literal(session.getValueFactory()
+                                       .createValue(username));
+                       Constraint constraint = qomf.comparison(userIdDop,
+                                       QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop);
+                       Query query = qomf.createQuery(userHomeSel, constraint, null, null);
+                       return JcrUtils.querySingleNode(query);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException(
+                                       "Cannot find profile for user " + username, e);
+               }
+       }
+
+       /** Returns the home node of the session user or null if none was found. */
+       public static Node getUserHome(Session session) {
+               String userID = session.getUserID();
+               return getUserHome(session, userID);
+       }
+
+       private UserJcrUtils() {
+       }
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/VersionDiff.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/VersionDiff.java
new file mode 100644 (file)
index 0000000..e6ae913
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr;
+
+import java.util.Calendar;
+import java.util.Map;
+
+/**
+ * Generic Object that enables the creation of history reports based on a JCR
+ * versionable node. userId and creation date are added to the map of
+ * PropertyDiff.
+ * 
+ * These two fields might be null
+ * 
+ */
+public class VersionDiff {
+
+       private String userId;
+       private Map<String, PropertyDiff> diffs;
+       private Calendar updateTime;
+
+       public VersionDiff(String userId, Calendar updateTime,
+                       Map<String, PropertyDiff> diffs) {
+               this.userId = userId;
+               this.updateTime = updateTime;
+               this.diffs = diffs;
+       }
+
+       public String getUserId() {
+               return userId;
+       }
+
+       public Map<String, PropertyDiff> getDiffs() {
+               return diffs;
+       }
+
+       public Calendar getUpdateTime() {
+               return updateTime;
+       }
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/argeo.cnd b/org.argeo.server.jcr/newsrc/org/argeo/jcr/argeo.cnd
new file mode 100644 (file)
index 0000000..fbfea9d
--- /dev/null
@@ -0,0 +1,72 @@
+<argeo = 'http://www.argeo.org/ns/argeo'>
+
+// GENERIC TYPES NOT AVAILABLE IN JCR
+[argeo:link] > mix:created, mix:lastModified
+mixin
+// URI(s)
+- argeo:uri (STRING) m
+
+[argeo:references] > nt:unstructured
+- * (REFERENCE) *
+
+// DATA MODEL
+[argeo:dataModel] > mix:created, mix:lastModified, mix:versionable
+mixin
+- argeo:uri (STRING) m
+- argeo:dataModelVersion (STRING) m
+
+// USER NODES
+// user should be lower case, between 3 and 15 characters long
+[argeo:userHome] > mix:created, mix:lastModified
+mixin
+- argeo:userID (STRING) m
+- argeo:remoteRoles (STRING) *
+// deprecated. for backward compatibility:
++ argeo:profile (argeo:userProfile)
++ argeo:keyring (argeo:pbeSpec)
++ argeo:preferences (argeo:preferenceNode)
+
+[argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable
+mixin
+- argeo:userID (STRING) m
+- argeo:enabled (BOOLEAN)
+- argeo:accountNonExpired (BOOLEAN)
+- argeo:accountNonLocked (BOOLEAN)
+- argeo:credentialsNonExpired (BOOLEAN)
+
+[argeo:preferenceNode] > mix:lastModified, mix:versionable
+mixin
++ * (argeo:preferenceNode) * version
+
+[argeo:remoteRepository] > nt:unstructured
+- argeo:uri (STRING)
+- argeo:userID (STRING)
++ argeo:password (argeo:encrypted)
+
+// TABULAR CONTENT
+[argeo:table] > nt:file
++ * (argeo:column) *
+
+[argeo:column] > mix:title
+- jcr:requiredType (STRING) = 'STRING'
+
+[argeo:csv] > nt:resource
+
+// CRYPTO
+[argeo:encrypted] > nt:base
+mixin
+// initialization vector used by some algorithms
+- argeo:iv (BINARY)
+
+[argeo:pbeKeySpec] > nt:base
+mixin
+- argeo:secretKeyFactory (STRING)
+- argeo:salt (BINARY)
+- argeo:iterationCount (LONG)
+- argeo:keyLength (LONG)
+- argeo:secretKeyEncryption (STRING)
+
+[argeo:pbeSpec] > argeo:pbeKeySpec
+mixin
+- argeo:cipher (STRING)
+
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/AbstractUrlProxy.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/AbstractUrlProxy.java
new file mode 100644 (file)
index 0000000..8a66f31
--- /dev/null
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.proxy;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.JcrUtils;
+
+/** Base class for URL based proxys. */
+public abstract class AbstractUrlProxy implements ResourceProxy {
+       private final static Log log = LogFactory.getLog(AbstractUrlProxy.class);
+
+       private Repository jcrRepository;
+       private Session jcrAdminSession;
+       private String proxyWorkspace = "proxy";
+
+       protected abstract Node retrieve(Session session, String path);
+
+       void init() {
+               try {
+                       jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository,
+                                       proxyWorkspace);
+                       beforeInitSessionSave(jcrAdminSession);
+                       if (jcrAdminSession.hasPendingChanges())
+                               jcrAdminSession.save();
+               } catch (Exception e) {
+                       JcrUtils.discardQuietly(jcrAdminSession);
+                       throw new ArgeoException("Cannot initialize Maven proxy", e);
+               }
+       }
+
+       /**
+        * Called before the (admin) session is saved at the end of the
+        * initialization. Does nothing by default, to be overridden.
+        */
+       protected void beforeInitSessionSave(Session session)
+                       throws RepositoryException {
+       }
+
+       void destroy() {
+               JcrUtils.logoutQuietly(jcrAdminSession);
+       }
+
+       /**
+        * Called before the (admin) session is logged out when resources are
+        * released. Does nothing by default, to be overridden.
+        */
+       protected void beforeDestroySessionLogout() throws RepositoryException {
+       }
+
+       public Node proxy(String path) {
+               // we open a JCR session with client credentials in order not to use the
+               // admin session in multiple thread or make it a bottleneck.
+               Node nodeAdmin = null;
+               Node nodeClient = null;
+               Session clientSession = null;
+               try {
+                       clientSession = jcrRepository.login(proxyWorkspace);
+                       if (!clientSession.itemExists(path)
+                                       || shouldUpdate(clientSession, path)) {
+                               nodeAdmin = retrieveAndSave(path);
+                               if (nodeAdmin != null)
+                                       nodeClient = clientSession.getNode(path);
+                       } else
+                               nodeClient = clientSession.getNode(path);
+                       return nodeClient;
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot proxy " + path, e);
+               } finally {
+                       if (nodeClient == null)
+                               JcrUtils.logoutQuietly(clientSession);
+               }
+       }
+
+       protected synchronized Node retrieveAndSave(String path) {
+               try {
+                       Node node = retrieve(jcrAdminSession, path);
+                       if (node == null)
+                               return null;
+                       jcrAdminSession.save();
+                       return node;
+               } catch (RepositoryException e) {
+                       JcrUtils.discardQuietly(jcrAdminSession);
+                       throw new ArgeoException("Cannot retrieve and save " + path, e);
+               } finally {
+                       notifyAll();
+               }
+       }
+
+       /** Session is not saved */
+       protected synchronized Node proxyUrl(Session session, String remoteUrl,
+                       String path) throws RepositoryException {
+               Node node = null;
+               if (session.itemExists(path)) {
+                       // throw new ArgeoException("Node " + path + " already exists");
+               }
+               InputStream in = null;
+               try {
+                       URL u = new URL(remoteUrl);
+                       in = u.openStream();
+                       node = importFile(session, path, in);
+               } catch (IOException e) {
+                       if (log.isDebugEnabled()) {
+                               log.debug("Cannot read " + remoteUrl + ", skipping... "
+                                               + e.getMessage());
+                               // log.trace("Cannot read because of ", e);
+                       }
+                       JcrUtils.discardQuietly(session);
+               } finally {
+                       IOUtils.closeQuietly(in);
+               }
+               return node;
+       }
+
+       protected synchronized Node importFile(Session session, String path,
+                       InputStream in) throws RepositoryException {
+               Binary binary = null;
+               try {
+                       Node content = null;
+                       Node node = null;
+                       if (!session.itemExists(path)) {
+                               node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE,
+                                               NodeType.NT_FOLDER, false);
+                               content = node.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
+                       } else {
+                               node = session.getNode(path);
+                               content = node.getNode(Node.JCR_CONTENT);
+                       }
+                       binary = session.getValueFactory().createBinary(in);
+                       content.setProperty(Property.JCR_DATA, binary);
+                       JcrUtils.updateLastModifiedAndParents(node, null);
+                       return node;
+               } finally {
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+
+       /** Whether the file should be updated. */
+       protected Boolean shouldUpdate(Session clientSession, String nodePath) {
+               return false;
+       }
+
+       public void setJcrRepository(Repository jcrRepository) {
+               this.jcrRepository = jcrRepository;
+       }
+
+       public void setProxyWorkspace(String localWorkspace) {
+               this.proxyWorkspace = localWorkspace;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/ResourceProxy.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/ResourceProxy.java
new file mode 100644 (file)
index 0000000..b4fb332
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.proxy;
+
+import javax.jcr.Node;
+
+/** A proxy which nows how to resolve and synchronize relative URLs */
+public interface ResourceProxy {
+       /**
+        * Proxy the file referenced by this relative path in the underlying
+        * repository. A new session is created by each call, so the underlying
+        * session of the returned node must be closed by the caller.
+        * 
+        * @return the proxied Node, <code>null</code> if the resource was not found
+        *         (e.g. HTTP 404)
+        */
+       public Node proxy(String relativePath);
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/security/JcrAuthorizations.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/security/JcrAuthorizations.java
new file mode 100644 (file)
index 0000000..491f8a6
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.security;
+
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.Privilege;
+
+import org.argeo.ArgeoException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.security.SimplePrincipal;
+
+/** Apply authorizations to a JCR repository. */
+public class JcrAuthorizations implements Runnable {
+       // private final static Log log =
+       // LogFactory.getLog(JcrAuthorizations.class);
+
+       private Repository repository;
+       private String workspace = null;
+
+       private String securityWorkspace = "security";
+
+       /**
+        * key := privilege1,privilege2/path/to/node<br/>
+        * value := group1,group2,user1
+        */
+       private Map<String, String> principalPrivileges = new HashMap<String, String>();
+
+       public void run() {
+               String currentWorkspace = workspace;
+               Session session = null;
+               try {
+                       if (workspace != null && workspace.equals("*")) {
+                               session = repository.login();
+                               String[] workspaces = session.getWorkspace()
+                                               .getAccessibleWorkspaceNames();
+                               JcrUtils.logoutQuietly(session);
+                               for (String wksp : workspaces) {
+                                       currentWorkspace = wksp;
+                                       if (currentWorkspace.equals(securityWorkspace))
+                                               continue;
+                                       session = repository.login(currentWorkspace);
+                                       initAuthorizations(session);
+                                       JcrUtils.logoutQuietly(session);
+                               }
+                       } else {
+                               session = repository.login(workspace);
+                               initAuthorizations(session);
+                       }
+               } catch (Exception e) {
+                       JcrUtils.discardQuietly(session);
+                       throw new ArgeoException(
+                                       "Cannot set authorizations " + principalPrivileges
+                                                       + " on workspace " + currentWorkspace, e);
+               } finally {
+                       JcrUtils.logoutQuietly(session);
+               }
+       }
+
+       protected void processWorkspace(String workspace) {
+               Session session = null;
+               try {
+                       session = repository.login(workspace);
+                       initAuthorizations(session);
+               } catch (Exception e) {
+                       JcrUtils.discardQuietly(session);
+                       throw new ArgeoException("Cannot set authorizations "
+                                       + principalPrivileges + " on repository " + repository, e);
+               } finally {
+                       JcrUtils.logoutQuietly(session);
+               }
+       }
+
+       /** @deprecated call {@link #run()} instead. */
+       @Deprecated
+       public void init() {
+               run();
+       }
+
+       protected void initAuthorizations(Session session)
+                       throws RepositoryException {
+               AccessControlManager acm = session.getAccessControlManager();
+
+               for (String privileges : principalPrivileges.keySet()) {
+                       String path = null;
+                       int slashIndex = privileges.indexOf('/');
+                       if (slashIndex == 0) {
+                               throw new ArgeoException("Privilege " + privileges
+                                               + " badly formatted it starts with /");
+                       } else if (slashIndex > 0) {
+                               path = privileges.substring(slashIndex);
+                               privileges = privileges.substring(0, slashIndex);
+                       }
+
+                       if (path == null)
+                               path = "/";
+
+                       List<Privilege> privs = new ArrayList<Privilege>();
+                       for (String priv : privileges.split(",")) {
+                               privs.add(acm.privilegeFromName(priv));
+                       }
+
+                       String principalNames = principalPrivileges.get(privileges);
+                       for (String principalName : principalNames.split(",")) {
+                               Principal principal = getOrCreatePrincipal(session,
+                                               principalName);
+                               JcrUtils.addPrivileges(session, path, principal, privs);
+                               // if (log.isDebugEnabled()) {
+                               // StringBuffer privBuf = new StringBuffer();
+                               // for (Privilege priv : privs)
+                               // privBuf.append(priv.getName());
+                               // log.debug("Added privileges " + privBuf + " to "
+                               // + principal.getName() + " on " + path + " in '"
+                               // + session.getWorkspace().getName() + "'");
+                               // }
+                       }
+               }
+
+               // if (log.isDebugEnabled())
+               // log.debug("JCR authorizations applied on '"
+               // + session.getWorkspace().getName() + "'");
+       }
+
+       /**
+        * Returns a {@link SimplePrincipal}, does not check whether it exists since
+        * such capabilities is not provided by the standard JCR API. Can be
+        * overridden to provide smarter handling
+        */
+       protected Principal getOrCreatePrincipal(Session session,
+                       String principalName) throws RepositoryException {
+               return new SimplePrincipal(principalName);
+       }
+
+       // public static void addPrivileges(Session session, Principal principal,
+       // String path, List<Privilege> privs) throws RepositoryException {
+       // AccessControlManager acm = session.getAccessControlManager();
+       // // search for an access control list
+       // AccessControlList acl = null;
+       // AccessControlPolicyIterator policyIterator = acm
+       // .getApplicablePolicies(path);
+       // if (policyIterator.hasNext()) {
+       // while (policyIterator.hasNext()) {
+       // AccessControlPolicy acp = policyIterator
+       // .nextAccessControlPolicy();
+       // if (acp instanceof AccessControlList)
+       // acl = ((AccessControlList) acp);
+       // }
+       // } else {
+       // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
+       // for (AccessControlPolicy acp : existingPolicies) {
+       // if (acp instanceof AccessControlList)
+       // acl = ((AccessControlList) acp);
+       // }
+       // }
+       //
+       // if (acl != null) {
+       // acl.addAccessControlEntry(principal,
+       // privs.toArray(new Privilege[privs.size()]));
+       // acm.setPolicy(path, acl);
+       // session.save();
+       // if (log.isDebugEnabled()) {
+       // StringBuffer buf = new StringBuffer("");
+       // for (int i = 0; i < privs.size(); i++) {
+       // if (i != 0)
+       // buf.append(',');
+       // buf.append(privs.get(i).getName());
+       // }
+       // log.debug("Added privilege(s) '" + buf + "' to '"
+       // + principal.getName() + "' on " + path
+       // + " from workspace '"
+       // + session.getWorkspace().getName() + "'");
+       // }
+       // } else {
+       // throw new ArgeoException("Don't know how to apply  privileges "
+       // + privs + " to " + principal + " on " + path
+       // + " from workspace '" + session.getWorkspace().getName()
+       // + "'");
+       // }
+       // }
+
+       @Deprecated
+       public void setGroupPrivileges(Map<String, String> groupPrivileges) {
+               this.principalPrivileges = groupPrivileges;
+       }
+
+       public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
+               this.principalPrivileges = principalPrivileges;
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+       public void setWorkspace(String workspace) {
+               this.workspace = workspace;
+       }
+
+       public void setSecurityWorkspace(String securityWorkspace) {
+               this.securityWorkspace = securityWorkspace;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/BeanNodeMapper.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/BeanNodeMapper.java
new file mode 100644 (file)
index 0000000..9f70f5c
--- /dev/null
@@ -0,0 +1,649 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.spring;
+
+import java.beans.PropertyDescriptor;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.GregorianCalendar;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+import javax.jcr.Binary;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyIterator;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.jcr.NodeMapper;
+import org.argeo.jcr.NodeMapperProvider;
+import org.springframework.beans.BeanWrapper;
+import org.springframework.beans.BeanWrapperImpl;
+
+public class BeanNodeMapper implements NodeMapper {
+       private final static Log log = LogFactory.getLog(BeanNodeMapper.class);
+
+       private final static String NODE_VALUE = "value";
+
+       // private String keyNode = "bean:key";
+       private String uuidProperty = "uuid";
+       private String classProperty = "class";
+
+       private Boolean versioning = false;
+       private Boolean strictUuidReference = false;
+
+       // TODO define a primaryNodeType Strategy
+       private String primaryNodeType = null;
+
+       private ClassLoader classLoader = getClass().getClassLoader();
+
+       private NodeMapperProvider nodeMapperProvider;
+
+       /**
+        * exposed method to retrieve a bean from a node
+        */
+       public Object load(Node node) {
+               try {
+                       if (nodeMapperProvider != null) {
+                               NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node);
+                               if (nodeMapper != this) {
+                                       return nodeMapper.load(node);
+                               }
+                       }
+                       return nodeToBean(node);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot load object from node " + node, e);
+               }
+       }
+
+       /** Update an existing node with an object */
+       public void update(Node node, Object obj) {
+               try {
+                       if (nodeMapperProvider != null) {
+
+                               NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node);
+                               if (nodeMapper != this) {
+                                       nodeMapper.update(node, obj);
+                               } else
+                                       beanToNode(createBeanWrapper(obj), node);
+                       } else
+                               beanToNode(createBeanWrapper(obj), node);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot update node " + node + " with "
+                                       + obj, e);
+               }
+       }
+
+       /**
+        * if no storage path is given; we use canonical path
+        * 
+        * @see this.storagePath()
+        */
+       public Node save(Session session, Object obj) {
+               return save(session, storagePath(obj), obj);
+       }
+
+       /**
+        * Create a new node to store an object. If the parentNode doesn't exist, it
+        * is created
+        * 
+        * the primaryNodeType may be initialized before
+        */
+       public Node save(Session session, String path, Object obj) {
+               try {
+                       final Node node;
+                       String parentPath = JcrUtils.parentPath(path);
+                       // find or create parent node
+                       Node parentNode;
+                       if (session.itemExists(path))
+                               parentNode = (Node) session.getItem(parentPath);
+                       else {
+                               parentNode = JcrUtils.mkdirs(session, parentPath, null, null,
+                                               versioning);
+                       }
+                       // create node
+
+                       if (primaryNodeType != null)
+                               node = parentNode.addNode(JcrUtils.lastPathElement(path),
+                                               primaryNodeType);
+                       else
+                               node = parentNode.addNode(JcrUtils.lastPathElement(path));
+
+                       // Check specific cases
+                       if (nodeMapperProvider != null) {
+                               NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node);
+                               if (nodeMapper != this) {
+                                       nodeMapper.update(node, obj);
+                                       return node;
+                               }
+                       }
+                       update(node, obj);
+                       return node;
+               } catch (ArgeoException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot save or update " + obj + " under "
+                                       + path, e);
+               }
+       }
+
+       /**
+        * Parse the FQN of a class to string with '/' delimiters Prefix the
+        * returned string with "/objects/"
+        */
+       public String storagePath(Object obj) {
+               String clss = obj.getClass().getName();
+               StringBuffer buf = new StringBuffer("/objects/");
+               StringTokenizer st = new StringTokenizer(clss, ".");
+               while (st.hasMoreTokens()) {
+                       buf.append(st.nextToken()).append('/');
+               }
+               buf.append(obj.toString());
+               return buf.toString();
+       }
+
+       @SuppressWarnings("unchecked")
+       /** 
+        * Transforms a node into an object of the class defined by classProperty Property
+        */
+       protected Object nodeToBean(Node node) throws RepositoryException {
+               if (log.isTraceEnabled())
+                       log.trace("Load     " + node);
+
+               try {
+                       String clssName = node.getProperty(classProperty).getValue()
+                                       .getString();
+
+                       BeanWrapper beanWrapper = createBeanWrapper(loadClass(clssName));
+
+                       // process properties
+                       PropertyIterator propIt = node.getProperties();
+                       props: while (propIt.hasNext()) {
+                               Property prop = propIt.nextProperty();
+                               if (!beanWrapper.isWritableProperty(prop.getName()))
+                                       continue props;
+
+                               PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(prop
+                                               .getName());
+                               Class<?> propClass = pd.getPropertyType();
+
+                               if (log.isTraceEnabled())
+                                       log.trace("Load " + prop + ", propClass=" + propClass
+                                                       + ", property descriptor=" + pd);
+
+                               // primitive list
+                               if (propClass != null && List.class.isAssignableFrom(propClass)) {
+                                       List<Object> lst = new ArrayList<Object>();
+                                       Class<?> valuesClass = classFromProperty(prop);
+                                       if (valuesClass != null)
+                                               for (Value value : prop.getValues()) {
+                                                       lst.add(asObject(value, valuesClass));
+                                               }
+                                       continue props;
+                               }
+
+                               // Case of other type of property accepted by jcr
+                               // Long, Double, String, Binary, Date, Boolean, Name
+                               Object value = asObject(prop.getValue(), pd.getPropertyType());
+                               if (value != null)
+                                       beanWrapper.setPropertyValue(prop.getName(), value);
+                       }
+
+                       // process children nodes
+                       NodeIterator nodeIt = node.getNodes();
+                       nodes: while (nodeIt.hasNext()) {
+                               Node childNode = nodeIt.nextNode();
+                               String name = childNode.getName();
+                               if (!beanWrapper.isWritableProperty(name))
+                                       continue nodes;
+
+                               PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name);
+                               Class<?> propClass = pd.getPropertyType();
+
+                               // objects list
+                               if (propClass != null && List.class.isAssignableFrom(propClass)) {
+                                       String lstClass = childNode.getProperty(classProperty)
+                                                       .getString();
+                                       List<Object> lst;
+                                       try {
+                                               lst = (List<Object>) loadClass(lstClass).newInstance();
+                                       } catch (Exception e) {
+                                               lst = new ArrayList<Object>();
+                                       }
+
+                                       if (childNode.hasNodes()) {
+                                               // Look for children nodes
+                                               NodeIterator valuesIt = childNode.getNodes();
+                                               while (valuesIt.hasNext()) {
+                                                       Node lstValueNode = valuesIt.nextNode();
+                                                       Object lstValue = nodeToBean(lstValueNode);
+                                                       lst.add(lstValue);
+                                               }
+                                       } else {
+                                               // look for a property with the same name which will
+                                               // provide
+                                               // primitives
+                                               Property childProp = childNode.getProperty(childNode
+                                                               .getName());
+                                               Class<?> valuesClass = classFromProperty(childProp);
+                                               if (valuesClass != null)
+                                                       if (childProp.getDefinition().isMultiple())
+                                                               for (Value value : childProp.getValues()) {
+                                                                       lst.add(asObject(value, valuesClass));
+                                                               }
+                                                       else
+                                                               lst.add(asObject(childProp.getValue(),
+                                                                               valuesClass));
+                                       }
+                                       beanWrapper.setPropertyValue(name, lst);
+                                       continue nodes;
+                               }
+
+                               // objects map
+                               if (propClass != null && Map.class.isAssignableFrom(propClass)) {
+                                       String mapClass = childNode.getProperty(classProperty)
+                                                       .getString();
+                                       Map<Object, Object> map;
+                                       try {
+                                               map = (Map<Object, Object>) loadClass(mapClass)
+                                                               .newInstance();
+                                       } catch (Exception e) {
+                                               map = new HashMap<Object, Object>();
+                                       }
+
+                                       // properties
+                                       PropertyIterator keysPropIt = childNode.getProperties();
+                                       keyProps: while (keysPropIt.hasNext()) {
+                                               Property keyProp = keysPropIt.nextProperty();
+                                               // FIXME: use property editor
+                                               String key = keyProp.getName();
+                                               if (classProperty.equals(key))
+                                                       continue keyProps;
+
+                                               Class<?> keyPropClass = classFromProperty(keyProp);
+                                               if (keyPropClass != null) {
+                                                       Object mapValue = asObject(keyProp.getValue(),
+                                                                       keyPropClass);
+                                                       map.put(key, mapValue);
+                                               }
+                                       }
+
+                                       // node
+                                       NodeIterator keysIt = childNode.getNodes();
+                                       while (keysIt.hasNext()) {
+                                               Node mapValueNode = keysIt.nextNode();
+                                               // FIXME: use property editor
+                                               Object key = mapValueNode.getName();
+
+                                               Object mapValue = nodeToBean(mapValueNode);
+
+                                               map.put(key, mapValue);
+                                       }
+                                       beanWrapper.setPropertyValue(name, map);
+                                       continue nodes;
+                               }
+
+                               // default
+                               Object value = nodeToBean(childNode);
+                               beanWrapper.setPropertyValue(name, value);
+
+                       }
+                       return beanWrapper.getWrappedInstance();
+               } catch (Exception e) {
+                       throw new ArgeoException("Cannot map node " + node, e);
+               }
+       }
+
+       /**
+        * Transforms an object to the specified jcr Node in order to persist it.
+        * 
+        * @param beanWrapper
+        * @param node
+        * @throws RepositoryException
+        */
+       protected void beanToNode(BeanWrapper beanWrapper, Node node)
+                       throws RepositoryException {
+               properties: for (PropertyDescriptor pd : beanWrapper
+                               .getPropertyDescriptors()) {
+                       String name = pd.getName();
+                       if (!beanWrapper.isReadableProperty(name))
+                               continue properties;// skip
+
+                       Object value = beanWrapper.getPropertyValue(name);
+                       if (value == null) {
+                               // remove values when updating
+                               if (node.hasProperty(name))
+                                       node.setProperty(name, (Value) null);
+                               if (node.hasNode(name))
+                                       node.getNode(name).remove();
+
+                               continue properties;
+                       }
+
+                       // if (uuidProperty != null && uuidProperty.equals(name)) {
+                       // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
+                       // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
+                       // continue properties;
+                       // }
+
+                       if ("class".equals(name)) {
+                               if (classProperty != null) {
+                                       node.setProperty(classProperty,
+                                                       ((Class<?>) value).getName());
+                                       // TODO: store a class hierarchy?
+                               }
+                               continue properties;
+                       }
+
+                       // Some bean reference other classes. We must deal with this case
+                       if (value instanceof Class<?>) {
+                               node.setProperty(name, ((Class<?>) value).getName());
+                               continue properties;
+                       }
+
+                       Value val = asValue(node.getSession(), value);
+                       if (val != null) {
+                               node.setProperty(name, val);
+                               continue properties;
+                       }
+
+                       if (value instanceof List<?>) {
+                               List<?> lst = (List<?>) value;
+                               addList(node, name, lst);
+                               continue properties;
+                       }
+
+                       if (value instanceof Map<?, ?>) {
+                               Map<?, ?> map = (Map<?, ?>) value;
+                               addMap(node, name, map);
+                               continue properties;
+                       }
+
+                       BeanWrapper child = createBeanWrapper(value);
+                       // TODO: delegate to another mapper
+
+                       // TODO: deal with references
+                       // Node childNode = findChildReference(session, child);
+                       // if (childNode != null) {
+                       // node.setProperty(name, childNode);
+                       // continue properties;
+                       // }
+
+                       // default case (recursive)
+                       if (node.hasNode(name)) {// update
+                               // TODO: optimize
+                               node.getNode(name).remove();
+                       }
+                       Node childNode = node.addNode(name);
+                       beanToNode(child, childNode);
+               }
+       }
+
+       /**
+        * Process specific case of list
+        * 
+        * @param node
+        * @param name
+        * @param lst
+        * @throws RepositoryException
+        */
+       protected void addList(Node node, String name, List<?> lst)
+                       throws RepositoryException {
+               if (node.hasNode(name)) {// update
+                       // TODO: optimize
+                       node.getNode(name).remove();
+               }
+
+               Node listNode = node.addNode(name);
+               listNode.setProperty(classProperty, lst.getClass().getName());
+               Value[] values = new Value[lst.size()];
+               boolean atLeastOneSet = false;
+               for (int i = 0; i < lst.size(); i++) {
+                       Object lstValue = lst.get(i);
+                       values[i] = asValue(node.getSession(), lstValue);
+                       if (values[i] != null) {
+                               atLeastOneSet = true;
+                       } else {
+                               Node childNode = findChildReference(node.getSession(),
+                                               createBeanWrapper(lstValue));
+                               if (childNode != null) {
+                                       values[i] = node.getSession().getValueFactory()
+                                                       .createValue(childNode);
+                                       atLeastOneSet = true;
+                               }
+                       }
+               }
+
+               // will be either properties or nodes, not both
+               if (!atLeastOneSet && lst.size() != 0) {
+                       for (Object lstValue : lst) {
+                               Node childNode = listNode.addNode(NODE_VALUE);
+                               beanToNode(createBeanWrapper(lstValue), childNode);
+                       }
+               } else {
+                       listNode.setProperty(name, values);
+               }
+       }
+
+       /**
+        * Process specific case of maps.
+        * 
+        * @param node
+        * @param name
+        * @param map
+        * @throws RepositoryException
+        */
+       protected void addMap(Node node, String name, Map<?, ?> map)
+                       throws RepositoryException {
+               if (node.hasNode(name)) {// update
+                       // TODO: optimize
+                       node.getNode(name).remove();
+               }
+
+               Node mapNode = node.addNode(name);
+               mapNode.setProperty(classProperty, map.getClass().getName());
+               for (Object key : map.keySet()) {
+                       Object mapValue = map.get(key);
+                       // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
+                       // null);
+                       String keyStr;
+                       // if (pe == null) {
+                       if (key instanceof CharSequence)
+                               keyStr = key.toString();
+                       else
+                               throw new ArgeoException(
+                                               "Cannot find property editor for class "
+                                                               + key.getClass());
+                       // } else {
+                       // pe.setValue(key);
+                       // keyStr = pe.getAsText();
+                       // }
+                       // TODO: check string format
+
+                       Value mapVal = asValue(node.getSession(), mapValue);
+                       if (mapVal != null)
+                               mapNode.setProperty(keyStr, mapVal);
+                       else {
+                               Node entryNode = mapNode.addNode(keyStr);
+                               beanToNode(createBeanWrapper(mapValue), entryNode);
+                       }
+
+               }
+
+       }
+
+       protected BeanWrapper createBeanWrapper(Object obj) {
+               return new BeanWrapperImpl(obj);
+       }
+
+       protected BeanWrapper createBeanWrapper(Class<?> clss) {
+               return new BeanWrapperImpl(clss);
+       }
+
+       /** Returns null if value cannot be found */
+       protected Value asValue(Session session, Object value)
+                       throws RepositoryException {
+               ValueFactory valueFactory = session.getValueFactory();
+               if (value instanceof Integer)
+                       return valueFactory.createValue((Integer) value);
+               else if (value instanceof Long)
+                       return valueFactory.createValue((Long) value);
+               else if (value instanceof Float)
+                       return valueFactory.createValue((Float) value);
+               else if (value instanceof Double)
+                       return valueFactory.createValue((Double) value);
+               else if (value instanceof Boolean)
+                       return valueFactory.createValue((Boolean) value);
+               else if (value instanceof Calendar)
+                       return valueFactory.createValue((Calendar) value);
+               else if (value instanceof Date) {
+                       Calendar cal = new GregorianCalendar();
+                       cal.setTime((Date) value);
+                       return valueFactory.createValue(cal);
+               } else if (value instanceof CharSequence)
+                       return valueFactory.createValue(value.toString());
+               else if (value instanceof InputStream) {
+                       Binary binary = session.getValueFactory().createBinary(
+                                       (InputStream) value);
+                       return valueFactory.createValue(binary);
+               } else
+                       return null;
+       }
+
+       protected Class<?> classFromProperty(Property property)
+                       throws RepositoryException {
+               switch (property.getType()) {
+               case PropertyType.LONG:
+                       return Long.class;
+               case PropertyType.DOUBLE:
+                       return Double.class;
+               case PropertyType.STRING:
+                       return String.class;
+               case PropertyType.BOOLEAN:
+                       return Boolean.class;
+               case PropertyType.DATE:
+                       return Calendar.class;
+               case PropertyType.NAME:
+                       return null;
+               default:
+                       throw new ArgeoException("Cannot find class for property "
+                                       + property + ", type="
+                                       + PropertyType.nameFromValue(property.getType()));
+               }
+       }
+
+       protected Object asObject(Value value, Class<?> propClass)
+                       throws RepositoryException {
+               if (propClass.equals(Integer.class))
+                       return (int) value.getLong();
+               else if (propClass.equals(Long.class))
+                       return value.getLong();
+               else if (propClass.equals(Float.class))
+                       return (float) value.getDouble();
+               else if (propClass.equals(Double.class))
+                       return value.getDouble();
+               else if (propClass.equals(Boolean.class))
+                       return value.getBoolean();
+               else if (CharSequence.class.isAssignableFrom(propClass))
+                       return value.getString();
+               else if (InputStream.class.isAssignableFrom(propClass))
+                       return value.getBinary().getStream();
+               else if (Calendar.class.isAssignableFrom(propClass))
+                       return value.getDate();
+               else if (Date.class.isAssignableFrom(propClass))
+                       return value.getDate().getTime();
+               else
+                       return null;
+       }
+
+       protected Node findChildReference(Session session, BeanWrapper child)
+                       throws RepositoryException {
+               if (child.isReadableProperty(uuidProperty)) {
+                       String childUuid = child.getPropertyValue(uuidProperty).toString();
+                       try {
+                               return session.getNodeByIdentifier(childUuid);
+                       } catch (ItemNotFoundException e) {
+                               if (strictUuidReference)
+                                       throw new ArgeoException("No node found with uuid "
+                                                       + childUuid, e);
+                       }
+               }
+               return null;
+       }
+
+       protected Class<?> loadClass(String name) {
+               // log.debug("Class loader: " + classLoader);
+               try {
+                       return classLoader.loadClass(name);
+               } catch (ClassNotFoundException e) {
+                       throw new ArgeoException("Cannot load class " + name, e);
+               }
+       }
+
+       protected String propertyName(String name) {
+               return name;
+       }
+
+       public void setVersioning(Boolean versioning) {
+               this.versioning = versioning;
+       }
+
+       public void setUuidProperty(String uuidProperty) {
+               this.uuidProperty = uuidProperty;
+       }
+
+       public void setClassProperty(String classProperty) {
+               this.classProperty = classProperty;
+       }
+
+       public void setStrictUuidReference(Boolean strictUuidReference) {
+               this.strictUuidReference = strictUuidReference;
+       }
+
+       public void setPrimaryNodeType(String primaryNodeType) {
+               this.primaryNodeType = primaryNodeType;
+       }
+
+       public void setClassLoader(ClassLoader classLoader) {
+               this.classLoader = classLoader;
+       }
+
+       public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider) {
+               this.nodeMapperProvider = nodeMapperProvider;
+       }
+
+       public String getPrimaryNodeType() {
+               return this.primaryNodeType;
+       }
+
+       public String getClassProperty() {
+               return this.classProperty;
+       }
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/ThreadBoundSession.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/ThreadBoundSession.java
new file mode 100644 (file)
index 0000000..a46bef1
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.spring;
+
+import org.argeo.jcr.ThreadBoundJcrSessionFactory;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.FactoryBean;
+import org.springframework.beans.factory.InitializingBean;
+
+public class ThreadBoundSession extends ThreadBoundJcrSessionFactory implements FactoryBean, InitializingBean, DisposableBean{
+       public void afterPropertiesSet() throws Exception {
+               init();
+       }
+
+       public void destroy() throws Exception {
+               dispose();
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularRowIterator.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularRowIterator.java
new file mode 100644 (file)
index 0000000..ca0635c
--- /dev/null
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.tabular;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ArrayBlockingQueue;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.util.CsvParser;
+import org.argeo.util.tabular.ArrayTabularRow;
+import org.argeo.util.tabular.TabularColumn;
+import org.argeo.util.tabular.TabularRow;
+import org.argeo.util.tabular.TabularRowIterator;
+
+/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */
+public class JcrTabularRowIterator implements TabularRowIterator {
+       private Boolean hasNext = null;
+       private Boolean parsingCompleted = false;
+
+       private Long currentRowNumber = 0l;
+
+       private List<TabularColumn> header = new ArrayList<TabularColumn>();
+
+       /** referenced so that we can close it */
+       private Binary binary;
+       private InputStream in;
+
+       private CsvParser csvParser;
+       private ArrayBlockingQueue<List<String>> textLines;
+
+       public JcrTabularRowIterator(Node tableNode) {
+               try {
+                       for (NodeIterator it = tableNode.getNodes(); it.hasNext();) {
+                               Node node = it.nextNode();
+                               if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) {
+                                       Integer type = PropertyType.valueFromName(node.getProperty(
+                                                       Property.JCR_REQUIRED_TYPE).getString());
+                                       TabularColumn tc = new TabularColumn(node.getProperty(
+                                                       Property.JCR_TITLE).getString(), type);
+                                       header.add(tc);
+                               }
+                       }
+                       Node contentNode = tableNode.getNode(Property.JCR_CONTENT);
+                       if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) {
+                               textLines = new ArrayBlockingQueue<List<String>>(1000);
+                               csvParser = new CsvParser() {
+                                       protected void processLine(Integer lineNumber,
+                                                       List<String> header, List<String> tokens) {
+                                               try {
+                                                       textLines.put(tokens);
+                                               } catch (InterruptedException e) {
+                                                       // TODO Auto-generated catch block
+                                                       e.printStackTrace();
+                                               }
+                                               // textLines.add(tokens);
+                                               if (hasNext == null) {
+                                                       hasNext = true;
+                                                       synchronized (JcrTabularRowIterator.this) {
+                                                               JcrTabularRowIterator.this.notifyAll();
+                                                       }
+                                               }
+                                       }
+                               };
+                               csvParser.setNoHeader(true);
+                               binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
+                               in = binary.getStream();
+                               Thread thread = new Thread(contentNode.getPath() + " reader") {
+                                       public void run() {
+                                               try {
+                                                       csvParser.parse(in);
+                                               } finally {
+                                                       parsingCompleted = true;
+                                                       IOUtils.closeQuietly(in);
+                                               }
+                                       }
+                               };
+                               thread.start();
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot read table " + tableNode, e);
+               }
+       }
+
+       public synchronized boolean hasNext() {
+               // we don't know if there is anything available
+               // while (hasNext == null)
+               // try {
+               // wait();
+               // } catch (InterruptedException e) {
+               // // silent
+               // // FIXME better deal with interruption
+               // Thread.currentThread().interrupt();
+               // break;
+               // }
+
+               // buffer not empty
+               if (!textLines.isEmpty())
+                       return true;
+
+               // maybe the parsing is finished but the flag has not been set
+               while (!parsingCompleted && textLines.isEmpty())
+                       try {
+                               wait(100);
+                       } catch (InterruptedException e) {
+                               // silent
+                               // FIXME better deal with interruption
+                               Thread.currentThread().interrupt();
+                               break;
+                       }
+
+               // buffer not empty
+               if (!textLines.isEmpty())
+                       return true;
+
+               // (parsingCompleted && textLines.isEmpty())
+               return false;
+
+               // if (!hasNext && textLines.isEmpty()) {
+               // if (in != null) {
+               // IOUtils.closeQuietly(in);
+               // in = null;
+               // }
+               // if (binary != null) {
+               // JcrUtils.closeQuietly(binary);
+               // binary = null;
+               // }
+               // return false;
+               // } else
+               // return true;
+       }
+
+       public synchronized TabularRow next() {
+               try {
+                       List<String> tokens = textLines.take();
+                       List<Object> objs = new ArrayList<Object>(tokens.size());
+                       for (String token : tokens) {
+                               // TODO convert to other formats using header
+                               objs.add(token);
+                       }
+                       currentRowNumber++;
+                       return new ArrayTabularRow(objs);
+               } catch (InterruptedException e) {
+                       // silent
+                       // FIXME better deal with interruption
+               }
+               return null;
+       }
+
+       public void remove() {
+               throw new UnsupportedOperationException();
+       }
+
+       public Long getCurrentRowNumber() {
+               return currentRowNumber;
+       }
+
+       public List<TabularColumn> getHeader() {
+               return header;
+       }
+
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularWriter.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularWriter.java
new file mode 100644 (file)
index 0000000..718ff23
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.tabular;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.util.List;
+
+import javax.jcr.Binary;
+import javax.jcr.Node;
+import javax.jcr.Property;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.io.IOUtils;
+import org.argeo.ArgeoException;
+import org.argeo.jcr.ArgeoTypes;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.util.CsvWriter;
+import org.argeo.util.tabular.TabularColumn;
+import org.argeo.util.tabular.TabularWriter;
+
+/** Write / reference tabular content in a JCR repository. */
+public class JcrTabularWriter implements TabularWriter {
+       private Node contentNode;
+       private ByteArrayOutputStream out;
+       private CsvWriter csvWriter;
+       
+       @SuppressWarnings("unused")
+       private final List<TabularColumn> columns;
+
+       /** Creates a table node */
+       public JcrTabularWriter(Node tableNode, List<TabularColumn> columns,
+                       String contentNodeType) {
+               try {
+                       this.columns = columns;
+                       for (TabularColumn column : columns) {
+                               String normalized = JcrUtils.replaceInvalidChars(column
+                                               .getName());
+                               Node columnNode = tableNode.addNode(normalized,
+                                               ArgeoTypes.ARGEO_COLUMN);
+                               columnNode.setProperty(Property.JCR_TITLE, column.getName());
+                               if (column.getType() != null)
+                                       columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
+                                                       PropertyType.nameFromValue(column.getType()));
+                               else
+                                       columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
+                                                       PropertyType.TYPENAME_STRING);
+                       }
+                       contentNode = tableNode.addNode(Property.JCR_CONTENT,
+                                       contentNodeType);
+                       if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) {
+                               contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv");
+                               contentNode.setProperty(Property.JCR_ENCODING, "UTF-8");
+                               out = new ByteArrayOutputStream();
+                               csvWriter = new CsvWriter(out);
+                       }
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot create table node " + tableNode, e);
+               }
+       }
+
+       public void appendRow(Object[] row) {
+               csvWriter.writeLine(row);
+       }
+
+       public void close() {
+               Binary binary = null;
+               InputStream in = null;
+               try {
+                       // TODO parallelize with pipes and writing from another thread
+                       in = new ByteArrayInputStream(out.toByteArray());
+                       binary = contentNode.getSession().getValueFactory()
+                                       .createBinary(in);
+                       contentNode.setProperty(Property.JCR_DATA, binary);
+               } catch (RepositoryException e) {
+                       throw new ArgeoException("Cannot store data in " + contentNode, e);
+               } finally {
+                       IOUtils.closeQuietly(in);
+                       JcrUtils.closeQuietly(binary);
+               }
+       }
+}
diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/unit/AbstractJcrTestCase.java
new file mode 100644 (file)
index 0000000..530605e
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2007-2012 Argeo GmbH
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *         http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.argeo.jcr.unit;
+
+import java.io.File;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+import javax.jcr.SimpleCredentials;
+
+import junit.framework.TestCase;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.ArgeoException;
+
+public abstract class AbstractJcrTestCase extends TestCase {
+       private final static Log log = LogFactory.getLog(AbstractJcrTestCase.class);
+
+       private Repository repository;
+       private Session session = null;
+
+       protected abstract File getRepositoryFile() throws Exception;
+
+       protected abstract Repository createRepository() throws Exception;
+
+       @Override
+       protected void setUp() throws Exception {
+               File homeDir = getHomeDir();
+               FileUtils.deleteDirectory(homeDir);
+               repository = createRepository();
+       }
+
+       protected File getHomeDir() {
+               File homeDir = new File(System.getProperty("java.io.tmpdir"),
+                               AbstractJcrTestCase.class.getSimpleName() + "-"
+                                               + System.getProperty("user.name"));
+               return homeDir;
+       }
+
+       @Override
+       protected void tearDown() throws Exception {
+               if (session != null) {
+                       session.logout();
+                       if (log.isDebugEnabled())
+                               log.debug("Logout session");
+               }
+       }
+
+       protected Session session() {
+               if (session == null) {
+                       try {
+                               if (log.isDebugEnabled())
+                                       log.debug("Login session");
+                               session = getRepository().login(
+                                               new SimpleCredentials("demo", "demo".toCharArray()));
+                       } catch (Exception e) {
+                               throw new ArgeoException("Cannot login to repository", e);
+                       }
+               }
+               return session;
+       }
+
+       protected Repository getRepository() {
+               return repository;
+       }
+
+       /**
+        * enables children class to set an existing repository in case it is not
+        * deleted on startup, to test migration by instance
+        */
+       protected void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrConstants.java
deleted file mode 100644 (file)
index b9b513a..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-/** Argeo model specific constants */
-public interface ArgeoJcrConstants {
-       public final static String ARGEO_BASE_PATH = "/argeo:system";
-       public final static String DATA_MODELS_BASE_PATH = ARGEO_BASE_PATH
-                       + "/argeo:dataModels";
-       public final static String PEOPLE_BASE_PATH = ARGEO_BASE_PATH
-                       + "/argeo:people";
-
-       // parameters (typically for call to a RepositoryFactory)
-       public final static String JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias";
-       public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri";
-
-       // standard aliases
-       public final static String ALIAS_NODE = "node";
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java
deleted file mode 100644 (file)
index dccb06c..0000000
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.HashMap;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.argeo.ArgeoException;
-
-/** Utilities related to Argeo model in JCR */
-public class ArgeoJcrUtils implements ArgeoJcrConstants {
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it
-        * and protect against future API changes.
-        */
-       public static Repository getRepositoryByAlias(
-                       RepositoryFactory repositoryFactory, String alias) {
-               try {
-                       Map<String, String> parameters = new HashMap<String, String>();
-                       parameters.put(JCR_REPOSITORY_ALIAS, alias);
-                       return repositoryFactory.getRepository(parameters);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Unexpected exception when trying to retrieve repository with alias "
-                                                       + alias, e);
-               }
-       }
-
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and
-        * protect against future API changes.
-        */
-       public static Repository getRepositoryByUri(
-                       RepositoryFactory repositoryFactory, String uri) {
-               return getRepositoryByUri(repositoryFactory, uri, null);
-       }
-
-       /**
-        * Wraps the call to the repository factory based on parameter
-        * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and
-        * protect against future API changes.
-        */
-       public static Repository getRepositoryByUri(
-                       RepositoryFactory repositoryFactory, String uri, String alias) {
-               try {
-                       Map<String, String> parameters = new HashMap<String, String>();
-                       parameters.put(JCR_REPOSITORY_URI, uri);
-                       if (alias != null)
-                               parameters.put(JCR_REPOSITORY_ALIAS, alias);
-                       return repositoryFactory.getRepository(parameters);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Unexpected exception when trying to retrieve repository with uri "
-                                                       + uri, e);
-               }
-       }
-
-       private ArgeoJcrUtils() {
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoNames.java
deleted file mode 100644 (file)
index 6e3eca9..0000000
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-/** JCR names in the http://www.argeo.org/argeo namespace */
-public interface ArgeoNames {
-       public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo";
-       public final static String ARGEO = "argeo";
-
-       public final static String ARGEO_URI = "argeo:uri";
-       public final static String ARGEO_USER_ID = "argeo:userID";
-       public final static String ARGEO_PREFERENCES = "argeo:preferences";
-       public final static String ARGEO_DATA_MODEL_VERSION = "argeo:dataModelVersion";
-
-       public final static String ARGEO_REMOTE = "argeo:remote";
-       public final static String ARGEO_PASSWORD = "argeo:password";
-       public final static String ARGEO_REMOTE_ROLES = "argeo:remoteRoles";
-
-       // user profile
-       public final static String ARGEO_PROFILE = "argeo:profile";
-
-       // spring security
-       public final static String ARGEO_ENABLED = "argeo:enabled";
-       public final static String ARGEO_ACCOUNT_NON_EXPIRED = "argeo:accountNonExpired";
-       public final static String ARGEO_ACCOUNT_NON_LOCKED = "argeo:accountNonLocked";
-       public final static String ARGEO_CREDENTIALS_NON_EXPIRED = "argeo:credentialsNonExpired";
-
-       // personal details
-       public final static String ARGEO_FIRST_NAME = "argeo:firstName";
-       public final static String ARGEO_LAST_NAME = "argeo:lastName";
-       public final static String ARGEO_PRIMARY_EMAIL = "argeo:primaryEmail";
-       public final static String ARGEO_PRIMARY_ORGANIZATION = "argeo:primaryOrganization";
-
-       // tabular
-       public final static String ARGEO_IS_KEY = "argeo:isKey";
-
-       // crypto
-       public final static String ARGEO_IV = "argeo:iv";
-       public final static String ARGEO_SECRET_KEY_FACTORY = "argeo:secretKeyFactory";
-       public final static String ARGEO_SALT = "argeo:salt";
-       public final static String ARGEO_ITERATION_COUNT = "argeo:iterationCount";
-       public final static String ARGEO_KEY_LENGTH = "argeo:keyLength";
-       public final static String ARGEO_SECRET_KEY_ENCRYPTION = "argeo:secretKeyEncryption";
-       public final static String ARGEO_CIPHER = "argeo:cipher";
-       public final static String ARGEO_KEYRING = "argeo:keyring";
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoTypes.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoTypes.java
deleted file mode 100644 (file)
index a11ead5..0000000
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-/** JCR types in the http://www.argeo.org/argeo namespace */
-public interface ArgeoTypes {
-       public final static String ARGEO_LINK = "argeo:link";
-       public final static String ARGEO_USER_HOME = "argeo:userHome";
-       public final static String ARGEO_USER_PROFILE = "argeo:userProfile";
-       public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository";
-       public final static String ARGEO_PREFERENCE_NODE = "argeo:preferenceNode";
-
-       // data model
-       public final static String ARGEO_DATA_MODEL = "argeo:dataModel";
-       
-       // tabular
-       public final static String ARGEO_TABLE = "argeo:table";
-       public final static String ARGEO_COLUMN = "argeo:column";
-       public final static String ARGEO_CSV = "argeo:csv";
-
-       // crypto
-       public final static String ARGEO_ENCRYPTED = "argeo:encrypted";
-       public final static String ARGEO_PBE_SPEC = "argeo:pbeSpec";
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/CollectionNodeIterator.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/CollectionNodeIterator.java
deleted file mode 100644 (file)
index a65907a..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.Collection;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-
-/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */
-public class CollectionNodeIterator implements NodeIterator {
-       private final Long collectionSize;
-       private final Iterator<Node> iterator;
-       private Integer position = 0;
-
-       public CollectionNodeIterator(Collection<Node> nodes) {
-               super();
-               this.collectionSize = (long) nodes.size();
-               this.iterator = nodes.iterator();
-       }
-
-       public void skip(long skipNum) {
-               if (skipNum < 0)
-                       throw new IllegalArgumentException(
-                                       "Skip count has to be positive: " + skipNum);
-
-               for (long i = 0; i < skipNum; i++) {
-                       if (!hasNext())
-                               throw new NoSuchElementException("Last element past (position="
-                                               + getPosition() + ")");
-                       nextNode();
-               }
-       }
-
-       public long getSize() {
-               return collectionSize;
-       }
-
-       public long getPosition() {
-               return position;
-       }
-
-       public boolean hasNext() {
-               return iterator.hasNext();
-       }
-
-       public Object next() {
-               return nextNode();
-       }
-
-       public void remove() {
-               iterator.remove();
-       }
-
-       public Node nextNode() {
-               Node node = iterator.next();
-               position++;
-               return node;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultJcrListener.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultJcrListener.java
deleted file mode 100644 (file)
index 253b305..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.observation.EventListener;
-import javax.jcr.observation.ObservationManager;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-
-/** To be overridden */
-public class DefaultJcrListener implements EventListener {
-       private final static Log log = LogFactory.getLog(DefaultJcrListener.class);
-       private Session session;
-       private String path = "/";
-       private Boolean deep = true;
-
-       public void start() {
-               try {
-                       addEventListener(session().getWorkspace().getObservationManager());
-                       if (log.isDebugEnabled())
-                               log.debug("Registered JCR event listener on " + path);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot register event listener", e);
-               }
-       }
-
-       public void stop() {
-               try {
-                       session().getWorkspace().getObservationManager()
-                                       .removeEventListener(this);
-                       if (log.isDebugEnabled())
-                               log.debug("Unregistered JCR event listener on " + path);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot unregister event listener", e);
-               }
-       }
-
-       /** Default is listen to all events */
-       protected Integer getEvents() {
-               return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED
-                               | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED;
-       }
-
-       /** To be overidden */
-       public void onEvent(EventIterator events) {
-               while (events.hasNext()) {
-                       Event event = events.nextEvent();
-                       log.debug(event);
-               }
-       }
-
-       /** To be overidden */
-       protected void addEventListener(ObservationManager observationManager)
-                       throws RepositoryException {
-               observationManager.addEventListener(this, getEvents(), path, deep,
-                               null, null, false);
-       }
-
-       private Session session() {
-               return session;
-       }
-
-       public void setPath(String path) {
-               this.path = path;
-       }
-
-       public void setDeep(Boolean deep) {
-               this.deep = deep;
-       }
-
-       public void setSession(Session session) {
-               this.session = session;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryFactory.java
deleted file mode 100644 (file)
index a7b30e7..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.Map;
-import java.util.Properties;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.RepositoryFactory;
-
-import org.argeo.ArgeoException;
-
-/**
- * Simple implementation of {@link RepositoryFactory}, supporting OSGi aliases.
- */
-public class DefaultRepositoryFactory extends DefaultRepositoryRegister
-               implements RepositoryFactory, ArgeoJcrConstants {
-       @SuppressWarnings("rawtypes")
-       public Repository getRepository(Map parameters) throws RepositoryException {
-               if (parameters.containsKey(JCR_REPOSITORY_ALIAS)) {
-                       String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString();
-                       return getRepositoryByAlias(alias);
-               } else if (parameters.containsKey(JCR_REPOSITORY_URI)) {
-                       String uri = parameters.get(JCR_REPOSITORY_URI).toString();
-                       return getRepositoryByAlias(getAliasFromURI(uri));
-               }
-               return null;
-       }
-
-       protected String getAliasFromURI(String uri) {
-               try {
-                       URI uriObj = new URI(uri);
-                       String alias = uriObj.getPath();
-                       if (alias.charAt(0) == '/')
-                               alias = alias.substring(1);
-                       if (alias.charAt(alias.length() - 1) == '/')
-                               alias = alias.substring(0, alias.length() - 1);
-                       return alias;
-               } catch (URISyntaxException e) {
-                       throw new ArgeoException("Cannot interpret URI " + uri, e);
-               }
-       }
-
-       /**
-        * Retrieve a repository by alias
-        * 
-        * @return the repository registered with alias or null if none
-        */
-       protected Repository getRepositoryByAlias(String alias) {
-               if (getRepositories().containsKey(alias))
-                       return getRepositories().get(alias);
-               else
-                       return null;
-       }
-
-       protected void publish(String alias, Repository repository,
-                       Properties properties) {
-               register(repository, properties);
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryRegister.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/DefaultRepositoryRegister.java
deleted file mode 100644 (file)
index f13c84e..0000000
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.Collections;
-import java.util.Map;
-import java.util.Observable;
-import java.util.TreeMap;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-
-public class DefaultRepositoryRegister extends Observable implements
-               RepositoryRegister, ArgeoJcrConstants {
-       private final static Log log = LogFactory
-                       .getLog(DefaultRepositoryRegister.class);
-
-       /** Read only map which will be directly exposed. */
-       private Map<String, Repository> repositories = Collections
-                       .unmodifiableMap(new TreeMap<String, Repository>());
-
-       @SuppressWarnings("rawtypes")
-       public synchronized Repository getRepository(Map parameters)
-                       throws RepositoryException {
-               if (!parameters.containsKey(JCR_REPOSITORY_ALIAS))
-                       throw new RepositoryException("Parameter " + JCR_REPOSITORY_ALIAS
-                                       + " has to be defined.");
-               String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString();
-               if (!repositories.containsKey(alias))
-                       throw new RepositoryException(
-                                       "No repository registered with alias " + alias);
-
-               return repositories.get(alias);
-       }
-
-       /** Access to the read-only map */
-       public synchronized Map<String, Repository> getRepositories() {
-               return repositories;
-       }
-
-       /** Registers a service, typically called when OSGi services are bound. */
-       @SuppressWarnings("rawtypes")
-       public synchronized void register(Repository repository, Map properties) {
-               // TODO: also check bean name?
-               String alias;
-               if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) {
-                       log.warn("Cannot register a repository if no "
-                                       + JCR_REPOSITORY_ALIAS + " property is speecified.");
-                       return;
-               }
-               alias = properties.get(JCR_REPOSITORY_ALIAS).toString();
-               Map<String, Repository> map = new TreeMap<String, Repository>(
-                               repositories);
-               map.put(alias, repository);
-               repositories = Collections.unmodifiableMap(map);
-               setChanged();
-               notifyObservers(alias);
-       }
-
-       /** Unregisters a service, typically called when OSGi services are unbound. */
-       @SuppressWarnings("rawtypes")
-       public synchronized void unregister(Repository repository, Map properties) {
-               // TODO: also check bean name?
-               if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) {
-                       log.warn("Cannot unregister a repository without property "
-                                       + JCR_REPOSITORY_ALIAS);
-                       return;
-               }
-
-               String alias = properties.get(JCR_REPOSITORY_ALIAS).toString();
-               Map<String, Repository> map = new TreeMap<String, Repository>(
-                               repositories);
-               map.put(alias, repository);
-               repositories = Collections.unmodifiableMap(map);
-               setChanged();
-               notifyObservers(alias);
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrCallback.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrCallback.java
deleted file mode 100644 (file)
index 0c4706f..0000000
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Session;
-
-/** An arbitrary execution on a JCR session, optionally returning a result. */
-public interface JcrCallback {
-       public Object execute(Session session);
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java
deleted file mode 100644 (file)
index f993c2f..0000000
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Credentials;
-import javax.jcr.LoginException;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-
-import org.argeo.ArgeoException;
-
-/**
- * Wrapper around a JCR repository which allows to simplify configuration and
- * intercept some actions. It exposes itself as a {@link Repository}.
- */
-public abstract class JcrRepositoryWrapper implements Repository {
-       // private final static Log log = LogFactory
-       // .getLog(JcrRepositoryWrapper.class);
-
-       // wrapped repository
-       private Repository repository;
-
-       private Boolean autocreateWorkspaces = false;
-
-       /**
-        * Empty constructor, {@link #init()} should be called after properties have
-        * been set
-        */
-       public JcrRepositoryWrapper() {
-       }
-
-       /** Initializes */
-       public void init() {
-       }
-
-       /** Shutdown the repository */
-       public void destroy() throws Exception {
-       }
-
-       /*
-        * DELEGATED JCR REPOSITORY METHODS
-        */
-
-       public String getDescriptor(String key) {
-               return getRepository().getDescriptor(key);
-       }
-
-       public String[] getDescriptorKeys() {
-               return getRepository().getDescriptorKeys();
-       }
-
-       /** Central login method */
-       public Session login(Credentials credentials, String workspaceName)
-                       throws LoginException, NoSuchWorkspaceException,
-                       RepositoryException {
-               Session session;
-               try {
-                       session = getRepository().login(credentials, workspaceName);
-               } catch (NoSuchWorkspaceException e) {
-                       if (autocreateWorkspaces && workspaceName != null)
-                               session = createWorkspaceAndLogsIn(credentials, workspaceName);
-                       else
-                               throw e;
-               }
-               processNewSession(session);
-               return session;
-       }
-
-       public Session login() throws LoginException, RepositoryException {
-               return login(null, null);
-       }
-
-       public Session login(Credentials credentials) throws LoginException,
-                       RepositoryException {
-               return login(credentials, null);
-       }
-
-       public Session login(String workspaceName) throws LoginException,
-                       NoSuchWorkspaceException, RepositoryException {
-               return login(null, workspaceName);
-       }
-
-       /** Called after a session has been created, does nothing by default. */
-       protected void processNewSession(Session session) {
-       }
-
-       /** Wraps access to the repository, making sure it is available. */
-       protected synchronized Repository getRepository() {
-//             if (repository == null) {
-//                     throw new ArgeoException("No repository initialized."
-//                                     + " Was the init() method called?"
-//                                     + " The destroy() method should also"
-//                                     + " be called on shutdown.");
-//             }
-               return repository;
-       }
-
-       /**
-        * Logs in to the default workspace, creates the required workspace, logs
-        * out, logs in to the required workspace.
-        */
-       protected Session createWorkspaceAndLogsIn(Credentials credentials,
-                       String workspaceName) throws RepositoryException {
-               if (workspaceName == null)
-                       throw new ArgeoException("No workspace specified.");
-               Session session = getRepository().login(credentials);
-               session.getWorkspace().createWorkspace(workspaceName);
-               session.logout();
-               return getRepository().login(credentials, workspaceName);
-       }
-
-       public boolean isStandardDescriptor(String key) {
-               return getRepository().isStandardDescriptor(key);
-       }
-
-       public boolean isSingleValueDescriptor(String key) {
-               return getRepository().isSingleValueDescriptor(key);
-       }
-
-       public Value getDescriptorValue(String key) {
-               return getRepository().getDescriptorValue(key);
-       }
-
-       public Value[] getDescriptorValues(String key) {
-               return getRepository().getDescriptorValues(key);
-       }
-
-       public synchronized void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) {
-               this.autocreateWorkspaces = autocreateWorkspaces;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrResourceAdapter.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrResourceAdapter.java
deleted file mode 100644 (file)
index 0b1a98c..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.version.Version;
-import javax.jcr.version.VersionHistory;
-import javax.jcr.version.VersionIterator;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-
-/**
- * Bridge Spring resources and JCR folder / files semantics (nt:folder /
- * nt:file), supporting versioning as well.
- */
-public class JcrResourceAdapter {
-       private final static Log log = LogFactory.getLog(JcrResourceAdapter.class);
-
-       private Session session;
-
-       private Boolean versioning = true;
-       private String defaultEncoding = "UTF-8";
-
-       // private String restoreBase = "/.restore";
-
-       public JcrResourceAdapter() {
-       }
-
-       public JcrResourceAdapter(Session session) {
-               this.session = session;
-       }
-
-       public void mkdirs(String path) {
-               JcrUtils.mkdirs(session(), path, NodeType.NT_FOLDER,
-                               NodeType.NT_FOLDER, versioning);
-       }
-
-       public void create(String path, InputStream in, String mimeType) {
-               try {
-                       if (session().itemExists(path)) {
-                               throw new ArgeoException("Node " + path + " already exists.");
-                       }
-
-                       int index = path.lastIndexOf('/');
-                       String parentPath = path.substring(0, index);
-                       if (parentPath.equals(""))
-                               parentPath = "/";
-                       String fileName = path.substring(index + 1);
-                       if (!session().itemExists(parentPath))
-                               throw new ArgeoException("Parent folder of node " + path
-                                               + " does not exist: " + parentPath);
-
-                       Node folderNode = (Node) session().getItem(parentPath);
-                       Node fileNode = folderNode.addNode(fileName, "nt:file");
-
-                       Node contentNode = fileNode.addNode(Property.JCR_CONTENT,
-                                       "nt:resource");
-                       if (mimeType != null)
-                               contentNode.setProperty(Property.JCR_MIMETYPE, mimeType);
-                       contentNode.setProperty(Property.JCR_ENCODING, defaultEncoding);
-                       Binary binary = session().getValueFactory().createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-                       JcrUtils.closeQuietly(binary);
-                       Calendar lastModified = Calendar.getInstance();
-                       // lastModified.setTimeInMillis(file.lastModified());
-                       contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified);
-                       // resNode.addMixin("mix:referenceable");
-
-                       if (versioning)
-                               fileNode.addMixin("mix:versionable");
-
-                       session().save();
-
-                       if (versioning)
-                               session().getWorkspace().getVersionManager()
-                                               .checkin(fileNode.getPath());
-
-                       if (log.isDebugEnabled())
-                               log.debug("Created " + path);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot create node for " + path, e);
-               }
-
-       }
-
-       public void update(String path, InputStream in) {
-               try {
-
-                       if (!session().itemExists(path)) {
-                               String type = null;
-                               // FIXME: using javax.activation leads to conflict between Java
-                               // 1.5 and 1.6 (since javax.activation was included in Java 1.6)
-                               // String type = new MimetypesFileTypeMap()
-                               // .getContentType(FilenameUtils.getName(path));
-                               create(path, in, type);
-                               return;
-                       }
-
-                       Node fileNode = (Node) session().getItem(path);
-                       Node contentNode = fileNode.getNode(Property.JCR_CONTENT);
-                       if (versioning)
-                               session().getWorkspace().getVersionManager()
-                                               .checkout(fileNode.getPath());
-                       Binary binary = session().getValueFactory().createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-                       JcrUtils.closeQuietly(binary);
-                       Calendar lastModified = Calendar.getInstance();
-                       // lastModified.setTimeInMillis(file.lastModified());
-                       contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified);
-
-                       session().save();
-                       if (versioning)
-                               session().getWorkspace().getVersionManager()
-                                               .checkin(fileNode.getPath());
-
-                       if (log.isDebugEnabled())
-                               log.debug("Updated " + path);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot update node " + path, e);
-               }
-       }
-
-       public List<Calendar> listVersions(String path) {
-               if (!versioning)
-                       throw new ArgeoException("Versioning is not activated");
-
-               try {
-                       List<Calendar> versions = new ArrayList<Calendar>();
-                       Node fileNode = (Node) session().getItem(path);
-                       VersionHistory history = session().getWorkspace()
-                                       .getVersionManager().getVersionHistory(fileNode.getPath());
-                       for (VersionIterator it = history.getAllVersions(); it.hasNext();) {
-                               Version version = (Version) it.next();
-                               versions.add(version.getCreated());
-                               if (log.isTraceEnabled()) {
-                                       log.debug(version);
-                                       // debug(version);
-                               }
-                       }
-                       return versions;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot list version of node " + path, e);
-               }
-       }
-
-       public InputStream retrieve(String path) {
-               try {
-                       Node node = (Node) session().getItem(
-                                       path + "/" + Property.JCR_CONTENT);
-                       Property property = node.getProperty(Property.JCR_DATA);
-                       return property.getBinary().getStream();
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot retrieve " + path, e);
-               }
-       }
-
-       public synchronized InputStream retrieve(String path, Integer revision) {
-               if (!versioning)
-                       throw new ArgeoException("Versioning is not activated");
-
-               try {
-                       Node fileNode = (Node) session().getItem(path);
-                       VersionHistory history = session().getWorkspace()
-                                       .getVersionManager().getVersionHistory(fileNode.getPath());
-                       int count = 0;
-                       Version version = null;
-                       for (VersionIterator it = history.getAllVersions(); it.hasNext();) {
-                               version = (Version) it.next();
-                               if (count == revision + 1) {
-                                       InputStream in = fromVersion(version);
-                                       if (log.isDebugEnabled())
-                                               log.debug("Retrieved " + path + " at revision "
-                                                               + revision);
-                                       return in;
-                               }
-                               count++;
-                       }
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot retrieve version " + revision
-                                       + " of " + path, e);
-               }
-
-               throw new ArgeoException("Version " + revision
-                               + " does not exist for node " + path);
-       }
-
-       protected InputStream fromVersion(Version version)
-                       throws RepositoryException {
-               Node frozenNode = version.getNode("jcr:frozenNode");
-               InputStream in = frozenNode.getNode(Property.JCR_CONTENT)
-                               .getProperty(Property.JCR_DATA).getBinary().getStream();
-               return in;
-       }
-
-       protected Session session() {
-               return session;
-       }
-
-       public void setVersioning(Boolean versioning) {
-               this.versioning = versioning;
-       }
-
-       public void setDefaultEncoding(String defaultEncoding) {
-               this.defaultEncoding = defaultEncoding;
-       }
-
-       protected String fill(Integer number) {
-               int size = 4;
-               String str = number.toString();
-               for (int i = str.length(); i < size; i++) {
-                       str = "0" + str;
-               }
-               return str;
-       }
-
-       public void setSession(Session session) {
-               this.session = session;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUrlStreamHandler.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUrlStreamHandler.java
deleted file mode 100644 (file)
index a777639..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-import java.net.URLConnection;
-import java.net.URLStreamHandler;
-
-import javax.jcr.Item;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */
-public class JcrUrlStreamHandler extends URLStreamHandler {
-       private final Session session;
-
-       public JcrUrlStreamHandler(Session session) {
-               this.session = session;
-       }
-
-       @Override
-       protected URLConnection openConnection(final URL u) throws IOException {
-               // TODO Auto-generated method stub
-               return new URLConnection(u) {
-
-                       @Override
-                       public void connect() throws IOException {
-                               String itemPath = u.getPath();
-                               try {
-                                       if (!session.itemExists(itemPath))
-                                               throw new IOException("No item under " + itemPath);
-
-                                       Item item = session.getItem(u.getPath());
-                                       if (item.isNode()) {
-                                               // this should be a nt:file node
-                                               Node node = (Node) item;
-                                               if (!node.getPrimaryNodeType().isNodeType(
-                                                               NodeType.NT_FILE))
-                                                       throw new IOException("Node " + node + " is not a "
-                                                                       + NodeType.NT_FILE);
-
-                                       } else {
-                                               Property property = (Property) item;
-                                               if(property.getType()==PropertyType.BINARY){
-                                                       //Binary binary = property.getBinary();
-                                                       
-                                               }
-                                       }
-                               } catch (RepositoryException e) {
-                                       IOException ioe = new IOException(
-                                                       "Unexpected JCR exception");
-                                       ioe.initCause(e);
-                                       throw ioe;
-                               }
-                       }
-
-                       @Override
-                       public InputStream getInputStream() throws IOException {
-                               // TODO Auto-generated method stub
-                               return super.getInputStream();
-                       }
-
-               };
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrUtils.java
deleted file mode 100644 (file)
index 2176e75..0000000
+++ /dev/null
@@ -1,1590 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.security.Principal;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-
-import javax.jcr.Binary;
-import javax.jcr.NamespaceRegistry;
-import javax.jcr.NoSuchWorkspaceException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.Workspace;
-import javax.jcr.nodetype.NodeType;
-import javax.jcr.observation.EventListener;
-import javax.jcr.query.Query;
-import javax.jcr.query.QueryResult;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlList;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.AccessControlPolicyIterator;
-import javax.jcr.security.Privilege;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.ArgeoMonitor;
-import org.argeo.util.security.DigestUtils;
-import org.argeo.util.security.SimplePrincipal;
-
-/** Utility methods to simplify common JCR operations. */
-public class JcrUtils implements ArgeoJcrConstants {
-
-       final private static Log log = LogFactory.getLog(JcrUtils.class);
-
-       /**
-        * Not complete yet. See
-        * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local
-        * %20Names
-        */
-       public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']',
-                       '|', '*', /*
-                                        * invalid XML chars :
-                                        */
-                       '<', '>', '&' };
-
-       /** Prevents instantiation */
-       private JcrUtils() {
-       }
-
-       /**
-        * Queries one single node.
-        * 
-        * @return one single node or null if none was found
-        * @throws ArgeoException
-        *             if more than one node was found
-        */
-       public static Node querySingleNode(Query query) {
-               NodeIterator nodeIterator;
-               try {
-                       QueryResult queryResult = query.execute();
-                       nodeIterator = queryResult.getNodes();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot execute query " + query, e);
-               }
-               Node node;
-               if (nodeIterator.hasNext())
-                       node = nodeIterator.nextNode();
-               else
-                       return null;
-
-               if (nodeIterator.hasNext())
-                       throw new ArgeoException("Query returned more than one node.");
-               return node;
-       }
-
-       /** Retrieves the node name from the provided path */
-       public static String nodeNameFromPath(String path) {
-               if (path.equals("/"))
-                       return "";
-               if (path.charAt(0) != '/')
-                       throw new ArgeoException("Path " + path + " must start with a '/'");
-               String pathT = path;
-               if (pathT.charAt(pathT.length() - 1) == '/')
-                       pathT = pathT.substring(0, pathT.length() - 2);
-
-               int index = pathT.lastIndexOf('/');
-               return pathT.substring(index + 1);
-       }
-
-       /** Retrieves the parent path of the provided path */
-       public static String parentPath(String path) {
-               if (path.equals("/"))
-                       throw new ArgeoException("Root path '/' has no parent path");
-               if (path.charAt(0) != '/')
-                       throw new ArgeoException("Path " + path + " must start with a '/'");
-               String pathT = path;
-               if (pathT.charAt(pathT.length() - 1) == '/')
-                       pathT = pathT.substring(0, pathT.length() - 2);
-
-               int index = pathT.lastIndexOf('/');
-               return pathT.substring(0, index);
-       }
-
-       /** The provided data as a path ('/' at the end, not the beginning) */
-       public static String dateAsPath(Calendar cal) {
-               return dateAsPath(cal, false);
-       }
-
-       /**
-        * Creates a deep path based on a URL:
-        * http://subdomain.example.com/to/content?args =>
-        * com/example/subdomain/to/content
-        */
-       public static String urlAsPath(String url) {
-               try {
-                       URL u = new URL(url);
-                       StringBuffer path = new StringBuffer(url.length());
-                       // invert host
-                       path.append(hostAsPath(u.getHost()));
-                       // we don't put port since it may not always be there and may change
-                       path.append(u.getPath());
-                       return path.toString();
-               } catch (MalformedURLException e) {
-                       throw new ArgeoException("Cannot generate URL path for " + url, e);
-               }
-       }
-
-       /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */
-       public static void urlToAddressProperties(Node node, String url) {
-               try {
-                       URL u = new URL(url);
-                       node.setProperty(Property.JCR_PROTOCOL, u.getProtocol());
-                       node.setProperty(Property.JCR_HOST, u.getHost());
-                       node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort()));
-                       node.setProperty(Property.JCR_PATH, normalizePath(u.getPath()));
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot set URL " + url
-                                       + " as nt:address properties", e);
-               }
-       }
-
-       /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */
-       public static String urlFromAddressProperties(Node node) {
-               try {
-                       URL u = new URL(
-                                       node.getProperty(Property.JCR_PROTOCOL).getString(), node
-                                                       .getProperty(Property.JCR_HOST).getString(),
-                                       (int) node.getProperty(Property.JCR_PORT).getLong(), node
-                                                       .getProperty(Property.JCR_PATH).getString());
-                       return u.toString();
-               } catch (Exception e) {
-                       throw new ArgeoException(
-                                       "Cannot get URL from nt:address properties of " + node, e);
-               }
-       }
-
-       /*
-        * PATH UTILITIES
-        */
-
-       /** Make sure that: starts with '/', do not end with '/', do not have '//' */
-       public static String normalizePath(String path) {
-               List<String> tokens = tokenize(path);
-               StringBuffer buf = new StringBuffer(path.length());
-               for (String token : tokens) {
-                       buf.append('/');
-                       buf.append(token);
-               }
-               return buf.toString();
-       }
-
-       /**
-        * Creates a path from a FQDN, inverting the order of the component:
-        * www.argeo.org => org.argeo.www
-        */
-       public static String hostAsPath(String host) {
-               StringBuffer path = new StringBuffer(host.length());
-               String[] hostTokens = host.split("\\.");
-               for (int i = hostTokens.length - 1; i >= 0; i--) {
-                       path.append(hostTokens[i]);
-                       if (i != 0)
-                               path.append('/');
-               }
-               return path.toString();
-       }
-
-       /**
-        * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c =>
-        * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning
-        */
-       public static String uuidAsPath(String uuid) {
-               StringBuffer path = new StringBuffer(uuid.length());
-               String[] tokens = uuid.split("-");
-               for (int i = 0; i < tokens.length; i++) {
-                       path.append(tokens[i]);
-                       if (i != 0)
-                               path.append('/');
-               }
-               return path.toString();
-       }
-
-       /**
-        * The provided data as a path ('/' at the end, not the beginning)
-        * 
-        * @param cal
-        *            the date
-        * @param addHour
-        *            whether to add hour as well
-        */
-       public static String dateAsPath(Calendar cal, Boolean addHour) {
-               StringBuffer buf = new StringBuffer(14);
-               buf.append('Y');
-               buf.append(cal.get(Calendar.YEAR));
-               buf.append('/');
-
-               int month = cal.get(Calendar.MONTH) + 1;
-               buf.append('M');
-               if (month < 10)
-                       buf.append(0);
-               buf.append(month);
-               buf.append('/');
-
-               int day = cal.get(Calendar.DAY_OF_MONTH);
-               buf.append('D');
-               if (day < 10)
-                       buf.append(0);
-               buf.append(day);
-               buf.append('/');
-
-               if (addHour) {
-                       int hour = cal.get(Calendar.HOUR_OF_DAY);
-                       buf.append('H');
-                       if (hour < 10)
-                               buf.append(0);
-                       buf.append(hour);
-                       buf.append('/');
-               }
-               return buf.toString();
-
-       }
-
-       /** Converts in one call a string into a gregorian calendar. */
-       public static Calendar parseCalendar(DateFormat dateFormat, String value) {
-               try {
-                       Date date = dateFormat.parse(value);
-                       Calendar calendar = new GregorianCalendar();
-                       calendar.setTime(date);
-                       return calendar;
-               } catch (ParseException e) {
-                       throw new ArgeoException("Cannot parse " + value
-                                       + " with date format " + dateFormat, e);
-               }
-
-       }
-
-       /** The last element of a path. */
-       public static String lastPathElement(String path) {
-               if (path.charAt(path.length() - 1) == '/')
-                       throw new ArgeoException("Path " + path + " cannot end with '/'");
-               int index = path.lastIndexOf('/');
-               if (index < 0)
-                       return path;
-               return path.substring(index + 1);
-       }
-
-       /**
-        * Call {@link Node#getName()} without exceptions (useful in super
-        * constructors).
-        */
-       public static String getNameQuietly(Node node) {
-               try {
-                       return node.getName();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot get name from " + node, e);
-               }
-       }
-
-       /**
-        * Call {@link Node#getProperty(String)} without exceptions (useful in super
-        * constructors).
-        */
-       public static String getStringPropertyQuietly(Node node, String propertyName) {
-               try {
-                       return node.getProperty(propertyName).getString();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot get name from " + node, e);
-               }
-       }
-
-       /**
-        * Routine that get the child with this name, adding id it does not already
-        * exist
-        */
-       public static Node getOrAdd(Node parent, String childName,
-                       String childPrimaryNodeType) throws RepositoryException {
-               return parent.hasNode(childName) ? parent.getNode(childName) : parent
-                               .addNode(childName, childPrimaryNodeType);
-       }
-
-       /**
-        * Routine that get the child with this name, adding id it does not already
-        * exist
-        */
-       public static Node getOrAdd(Node parent, String childName)
-                       throws RepositoryException {
-               return parent.hasNode(childName) ? parent.getNode(childName) : parent
-                               .addNode(childName);
-       }
-
-       /** Convert a {@link NodeIterator} to a list of {@link Node} */
-       public static List<Node> nodeIteratorToList(NodeIterator nodeIterator) {
-               List<Node> nodes = new ArrayList<Node>();
-               while (nodeIterator.hasNext()) {
-                       nodes.add(nodeIterator.nextNode());
-               }
-               return nodes;
-       }
-
-       /*
-        * PROPERTIES
-        */
-
-       /**
-        * Concisely get the string value of a property or null if this node doesn't
-        * have this property
-        */
-       public static String get(Node node, String propertyName) {
-               try {
-                       if (!node.hasProperty(propertyName))
-                               return null;
-                       return node.getProperty(propertyName).getString();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot get property " + propertyName
-                                       + " of " + node, e);
-               }
-       }
-
-       /** Concisely get the boolean value of a property */
-       public static Boolean check(Node node, String propertyName) {
-               try {
-                       return node.getProperty(propertyName).getBoolean();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot get property " + propertyName
-                                       + " of " + node, e);
-               }
-       }
-
-       /** Concisely get the bytes array value of a property */
-       public static byte[] getBytes(Node node, String propertyName) {
-               try {
-                       return getBinaryAsBytes(node.getProperty(propertyName));
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot get property " + propertyName
-                                       + " of " + node, e);
-               }
-       }
-
-       /** Creates the nodes making path, if they don't exist. */
-       public static Node mkdirs(Session session, String path) {
-               return mkdirs(session, path, null, null, false);
-       }
-
-       /**
-        * use {@link #mkdirs(Session, String, String, String, Boolean)} instead.
-        * 
-        * @deprecated
-        */
-       @Deprecated
-       public static Node mkdirs(Session session, String path, String type,
-                       Boolean versioning) {
-               return mkdirs(session, path, type, type, false);
-       }
-
-       /**
-        * @param type
-        *            the type of the leaf node
-        */
-       public static Node mkdirs(Session session, String path, String type) {
-               return mkdirs(session, path, type, null, false);
-       }
-
-       /**
-        * Create sub nodes relative to a parent node
-        * 
-        * @param nodeType
-        *            the type of the leaf node
-        */
-       public static Node mkdirs(Node parentNode, String relativePath,
-                       String nodeType) {
-               return mkdirs(parentNode, relativePath, nodeType, null);
-       }
-
-       /**
-        * Create sub nodes relative to a parent node
-        * 
-        * @param nodeType
-        *            the type of the leaf node
-        */
-       public static Node mkdirs(Node parentNode, String relativePath,
-                       String nodeType, String intermediaryNodeType) {
-               List<String> tokens = tokenize(relativePath);
-               Node currParent = parentNode;
-               try {
-                       for (int i = 0; i < tokens.size(); i++) {
-                               String name = tokens.get(i);
-                               if (currParent.hasNode(name)) {
-                                       currParent = currParent.getNode(name);
-                               } else {
-                                       if (i != (tokens.size() - 1)) {// intermediary
-                                               currParent = currParent.addNode(name,
-                                                               intermediaryNodeType);
-                                       } else {// leaf
-                                               currParent = currParent.addNode(name, nodeType);
-                                       }
-                               }
-                       }
-                       return currParent;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot mkdirs relative path "
-                                       + relativePath + " from " + parentNode, e);
-               }
-       }
-
-       /**
-        * Synchronized and save is performed, to avoid race conditions in
-        * initializers leading to duplicate nodes.
-        */
-       public synchronized static Node mkdirsSafe(Session session, String path,
-                       String type) {
-               try {
-                       if (session.hasPendingChanges())
-                               throw new ArgeoException(
-                                               "Session has pending changes, save them first.");
-                       Node node = mkdirs(session, path, type);
-                       session.save();
-                       return node;
-               } catch (RepositoryException e) {
-                       discardQuietly(session);
-                       throw new ArgeoException("Cannot safely make directories", e);
-               }
-       }
-
-       public synchronized static Node mkdirsSafe(Session session, String path) {
-               return mkdirsSafe(session, path, null);
-       }
-
-       /**
-        * Creates the nodes making path, if they don't exist. This is up to the
-        * caller to save the session. Use with caution since it can create
-        * duplicate nodes if used concurrently.
-        */
-       public static Node mkdirs(Session session, String path, String type,
-                       String intermediaryNodeType, Boolean versioning) {
-               try {
-                       if (path.equals('/'))
-                               return session.getRootNode();
-
-                       if (session.itemExists(path)) {
-                               Node node = session.getNode(path);
-                               // check type
-                               if (type != null && !node.isNodeType(type)
-                                               && !node.getPath().equals("/"))
-                                       throw new ArgeoException("Node " + node
-                                                       + " exists but is of type "
-                                                       + node.getPrimaryNodeType().getName()
-                                                       + " not of type " + type);
-                               // TODO: check versioning
-                               return node;
-                       }
-
-                       StringBuffer current = new StringBuffer("/");
-                       Node currentNode = session.getRootNode();
-                       Iterator<String> it = tokenize(path).iterator();
-                       while (it.hasNext()) {
-                               String part = it.next();
-                               current.append(part).append('/');
-                               if (!session.itemExists(current.toString())) {
-                                       if (!it.hasNext() && type != null)
-                                               currentNode = currentNode.addNode(part, type);
-                                       else if (it.hasNext() && intermediaryNodeType != null)
-                                               currentNode = currentNode.addNode(part,
-                                                               intermediaryNodeType);
-                                       else
-                                               currentNode = currentNode.addNode(part);
-                                       if (versioning)
-                                               currentNode.addMixin(NodeType.MIX_VERSIONABLE);
-                                       if (log.isTraceEnabled())
-                                               log.debug("Added folder " + part + " as " + current);
-                               } else {
-                                       currentNode = (Node) session.getItem(current.toString());
-                               }
-                       }
-                       return currentNode;
-               } catch (RepositoryException e) {
-                       discardQuietly(session);
-                       throw new ArgeoException("Cannot mkdirs " + path, e);
-               } finally {
-               }
-       }
-
-       /** Convert a path to the list of its tokens */
-       public static List<String> tokenize(String path) {
-               List<String> tokens = new ArrayList<String>();
-               boolean optimized = false;
-               if (!optimized) {
-                       String[] rawTokens = path.split("/");
-                       for (String token : rawTokens) {
-                               if (!token.equals(""))
-                                       tokens.add(token);
-                       }
-               } else {
-                       StringBuffer curr = new StringBuffer();
-                       char[] arr = path.toCharArray();
-                       chars: for (int i = 0; i < arr.length; i++) {
-                               char c = arr[i];
-                               if (c == '/') {
-                                       if (i == 0 || (i == arr.length - 1))
-                                               continue chars;
-                                       if (curr.length() > 0) {
-                                               tokens.add(curr.toString());
-                                               curr = new StringBuffer();
-                                       }
-                               } else
-                                       curr.append(c);
-                       }
-                       if (curr.length() > 0) {
-                               tokens.add(curr.toString());
-                               curr = new StringBuffer();
-                       }
-               }
-               return Collections.unmodifiableList(tokens);
-       }
-
-       /**
-        * Safe and repository implementation independent registration of a
-        * namespace.
-        */
-       public static void registerNamespaceSafely(Session session, String prefix,
-                       String uri) {
-               try {
-                       registerNamespaceSafely(session.getWorkspace()
-                                       .getNamespaceRegistry(), prefix, uri);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot find namespace registry", e);
-               }
-       }
-
-       /**
-        * Safe and repository implementation independent registration of a
-        * namespace.
-        */
-       public static void registerNamespaceSafely(NamespaceRegistry nr,
-                       String prefix, String uri) {
-               try {
-                       String[] prefixes = nr.getPrefixes();
-                       for (String pref : prefixes)
-                               if (pref.equals(prefix)) {
-                                       String registeredUri = nr.getURI(pref);
-                                       if (!registeredUri.equals(uri))
-                                               throw new ArgeoException("Prefix " + pref
-                                                               + " already registered for URI "
-                                                               + registeredUri
-                                                               + " which is different from provided URI "
-                                                               + uri);
-                                       else
-                                               return;// skip
-                               }
-                       nr.registerNamespace(prefix, uri);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot register namespace " + uri
-                                       + " under prefix " + prefix, e);
-               }
-       }
-
-       /** Recursively outputs the contents of the given node. */
-       public static void debug(Node node) {
-               debug(node, log);
-       }
-
-       /** Recursively outputs the contents of the given node. */
-       public static void debug(Node node, Log log) {
-               try {
-                       // First output the node path
-                       log.debug(node.getPath());
-                       // Skip the virtual (and large!) jcr:system subtree
-                       if (node.getName().equals("jcr:system")) {
-                               return;
-                       }
-
-                       // Then the children nodes (recursive)
-                       NodeIterator it = node.getNodes();
-                       while (it.hasNext()) {
-                               Node childNode = it.nextNode();
-                               debug(childNode, log);
-                       }
-
-                       // Then output the properties
-                       PropertyIterator properties = node.getProperties();
-                       // log.debug("Property are : ");
-
-                       properties: while (properties.hasNext()) {
-                               Property property = properties.nextProperty();
-                               if (property.getType() == PropertyType.BINARY)
-                                       continue properties;// skip
-                               if (property.getDefinition().isMultiple()) {
-                                       // A multi-valued property, print all values
-                                       Value[] values = property.getValues();
-                                       for (int i = 0; i < values.length; i++) {
-                                               log.debug(property.getPath() + "="
-                                                               + values[i].getString());
-                                       }
-                               } else {
-                                       // A single-valued property
-                                       log.debug(property.getPath() + "=" + property.getString());
-                               }
-                       }
-               } catch (Exception e) {
-                       log.error("Could not debug " + node, e);
-               }
-
-       }
-
-       /** Logs the effective access control policies */
-       public static void logEffectiveAccessPolicies(Node node) {
-               try {
-                       logEffectiveAccessPolicies(node.getSession(), node.getPath());
-               } catch (RepositoryException e) {
-                       log.error("Cannot log effective access policies of " + node, e);
-               }
-       }
-
-       /** Logs the effective access control policies */
-       public static void logEffectiveAccessPolicies(Session session, String path) {
-               if (!log.isDebugEnabled())
-                       return;
-
-               try {
-                       AccessControlPolicy[] effectivePolicies = session
-                                       .getAccessControlManager().getEffectivePolicies(path);
-                       if (effectivePolicies.length > 0) {
-                               for (AccessControlPolicy policy : effectivePolicies) {
-                                       if (policy instanceof AccessControlList) {
-                                               AccessControlList acl = (AccessControlList) policy;
-                                               log.debug("Access control list for " + path + "\n"
-                                                               + accessControlListSummary(acl));
-                                       }
-                               }
-                       } else {
-                               log.debug("No effective access control policy for " + path);
-                       }
-               } catch (RepositoryException e) {
-                       log.error("Cannot log effective access policies of " + path, e);
-               }
-       }
-
-       /** Returns a human-readable summary of this access control list. */
-       public static String accessControlListSummary(AccessControlList acl) {
-               StringBuffer buf = new StringBuffer("");
-               try {
-                       for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-                               buf.append('\t').append(ace.getPrincipal().getName())
-                                               .append('\n');
-                               for (Privilege priv : ace.getPrivileges())
-                                       buf.append("\t\t").append(priv.getName()).append('\n');
-                       }
-                       return buf.toString();
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot write summary of " + acl, e);
-               }
-       }
-
-       /**
-        * Copies recursively the content of a node to another one. Do NOT copy the
-        * property values of {@link NodeType#MIX_CREATED} and
-        * {@link NodeType#MIX_LAST_MODIFIED}, but update the
-        * {@link Property#JCR_LAST_MODIFIED} and
-        * {@link Property#JCR_LAST_MODIFIED_BY} properties if the target node has
-        * the {@link NodeType#MIX_LAST_MODIFIED} mixin.
-        */
-       public static void copy(Node fromNode, Node toNode) {
-               try {
-                       if (toNode.getDefinition().isProtected())
-                               return;
-
-                       // process properties
-                       PropertyIterator pit = fromNode.getProperties();
-                       properties: while (pit.hasNext()) {
-                               Property fromProperty = pit.nextProperty();
-                               String propertyName = fromProperty.getName();
-                               if (toNode.hasProperty(propertyName)
-                                               && toNode.getProperty(propertyName).getDefinition()
-                                                               .isProtected())
-                                       continue properties;
-
-                               if (fromProperty.getDefinition().isProtected())
-                                       continue properties;
-
-                               if (propertyName.equals("jcr:created")
-                                               || propertyName.equals("jcr:createdBy")
-                                               || propertyName.equals("jcr:lastModified")
-                                               || propertyName.equals("jcr:lastModifiedBy"))
-                                       continue properties;
-
-                               if (fromProperty.isMultiple()) {
-                                       toNode.setProperty(propertyName, fromProperty.getValues());
-                               } else {
-                                       toNode.setProperty(propertyName, fromProperty.getValue());
-                               }
-                       }
-
-                       // update jcr:lastModified and jcr:lastModifiedBy in toNode in case
-                       // they existed, before adding the mixins
-                       updateLastModified(toNode);
-
-                       // add mixins
-                       for (NodeType mixinType : fromNode.getMixinNodeTypes()) {
-                               toNode.addMixin(mixinType.getName());
-                       }
-
-                       // process children nodes
-                       NodeIterator nit = fromNode.getNodes();
-                       while (nit.hasNext()) {
-                               Node fromChild = nit.nextNode();
-                               Integer index = fromChild.getIndex();
-                               String nodeRelPath = fromChild.getName() + "[" + index + "]";
-                               Node toChild;
-                               if (toNode.hasNode(nodeRelPath))
-                                       toChild = toNode.getNode(nodeRelPath);
-                               else
-                                       toChild = toNode.addNode(fromChild.getName(), fromChild
-                                                       .getPrimaryNodeType().getName());
-                               copy(fromChild, toChild);
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot copy " + fromNode + " to "
-                                       + toNode, e);
-               }
-       }
-
-       /**
-        * Check whether all first-level properties (except jcr:* properties) are
-        * equal. Skip jcr:* properties
-        */
-       public static Boolean allPropertiesEquals(Node reference, Node observed,
-                       Boolean onlyCommonProperties) {
-               try {
-                       PropertyIterator pit = reference.getProperties();
-                       props: while (pit.hasNext()) {
-                               Property propReference = pit.nextProperty();
-                               String propName = propReference.getName();
-                               if (propName.startsWith("jcr:"))
-                                       continue props;
-
-                               if (!observed.hasProperty(propName))
-                                       if (onlyCommonProperties)
-                                               continue props;
-                                       else
-                                               return false;
-                               // TODO: deal with multiple property values?
-                               if (!observed.getProperty(propName).getValue()
-                                               .equals(propReference.getValue()))
-                                       return false;
-                       }
-                       return true;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot check all properties equals of "
-                                       + reference + " and " + observed, e);
-               }
-       }
-
-       public static Map<String, PropertyDiff> diffProperties(Node reference,
-                       Node observed) {
-               Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
-               diffPropertiesLevel(diffs, null, reference, observed);
-               return diffs;
-       }
-
-       /**
-        * Compare the properties of two nodes. Recursivity to child nodes is not
-        * yet supported. Skip jcr:* properties.
-        */
-       static void diffPropertiesLevel(Map<String, PropertyDiff> diffs,
-                       String baseRelPath, Node reference, Node observed) {
-               try {
-                       // check removed and modified
-                       PropertyIterator pit = reference.getProperties();
-                       props: while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               String name = p.getName();
-                               if (name.startsWith("jcr:"))
-                                       continue props;
-
-                               if (!observed.hasProperty(name)) {
-                                       String relPath = propertyRelPath(baseRelPath, name);
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED,
-                                                       relPath, p.getValue(), null);
-                                       diffs.put(relPath, pDiff);
-                               } else {
-                                       if (p.isMultiple()) {
-                                               // FIXME implement multiple
-                                       } else {
-                                               Value referenceValue = p.getValue();
-                                               Value newValue = observed.getProperty(name).getValue();
-                                               if (!referenceValue.equals(newValue)) {
-                                                       String relPath = propertyRelPath(baseRelPath, name);
-                                                       PropertyDiff pDiff = new PropertyDiff(
-                                                                       PropertyDiff.MODIFIED, relPath,
-                                                                       referenceValue, newValue);
-                                                       diffs.put(relPath, pDiff);
-                                               }
-                                       }
-                               }
-                       }
-                       // check added
-                       pit = observed.getProperties();
-                       props: while (pit.hasNext()) {
-                               Property p = pit.nextProperty();
-                               String name = p.getName();
-                               if (name.startsWith("jcr:"))
-                                       continue props;
-                               if (!reference.hasProperty(name)) {
-                                       if (p.isMultiple()) {
-                                               // FIXME implement multiple
-                                       } else {
-                                               String relPath = propertyRelPath(baseRelPath, name);
-                                               PropertyDiff pDiff = new PropertyDiff(
-                                                               PropertyDiff.ADDED, relPath, null, p.getValue());
-                                               diffs.put(relPath, pDiff);
-                                       }
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot diff " + reference + " and "
-                                       + observed, e);
-               }
-       }
-
-       /**
-        * Compare only a restricted list of properties of two nodes. No
-        * recursivity.
-        * 
-        */
-       public static Map<String, PropertyDiff> diffProperties(Node reference,
-                       Node observed, List<String> properties) {
-               Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
-               try {
-                       Iterator<String> pit = properties.iterator();
-
-                       props: while (pit.hasNext()) {
-                               String name = pit.next();
-                               if (!reference.hasProperty(name)) {
-                                       if (!observed.hasProperty(name))
-                                               continue props;
-                                       Value val = observed.getProperty(name).getValue();
-                                       try {
-                                               // empty String but not null
-                                               if ("".equals(val.getString()))
-                                                       continue props;
-                                       } catch (Exception e) {
-                                               // not parseable as String, silent
-                                       }
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED,
-                                                       name, null, val);
-                                       diffs.put(name, pDiff);
-                               } else if (!observed.hasProperty(name)) {
-                                       PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED,
-                                                       name, reference.getProperty(name).getValue(), null);
-                                       diffs.put(name, pDiff);
-                               } else {
-                                       Value referenceValue = reference.getProperty(name)
-                                                       .getValue();
-                                       Value newValue = observed.getProperty(name).getValue();
-                                       if (!referenceValue.equals(newValue)) {
-                                               PropertyDiff pDiff = new PropertyDiff(
-                                                               PropertyDiff.MODIFIED, name, referenceValue,
-                                                               newValue);
-                                               diffs.put(name, pDiff);
-                                       }
-                               }
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot diff " + reference + " and "
-                                       + observed, e);
-               }
-               return diffs;
-       }
-
-       /** Builds a property relPath to be used in the diff. */
-       private static String propertyRelPath(String baseRelPath,
-                       String propertyName) {
-               if (baseRelPath == null)
-                       return propertyName;
-               else
-                       return baseRelPath + '/' + propertyName;
-       }
-
-       /**
-        * Normalizes a name so that it can be stored in contexts not supporting
-        * names with ':' (typically databases). Replaces ':' by '_'.
-        */
-       public static String normalize(String name) {
-               return name.replace(':', '_');
-       }
-
-       /**
-        * Replaces characters which are invalid in a JCR name by '_'. Currently not
-        * exhaustive.
-        * 
-        * @see JcrUtils#INVALID_NAME_CHARACTERS
-        */
-       public static String replaceInvalidChars(String name) {
-               return replaceInvalidChars(name, '_');
-       }
-
-       /**
-        * Replaces characters which are invalid in a JCR name. Currently not
-        * exhaustive.
-        * 
-        * @see JcrUtils#INVALID_NAME_CHARACTERS
-        */
-       public static String replaceInvalidChars(String name, char replacement) {
-               boolean modified = false;
-               char[] arr = name.toCharArray();
-               for (int i = 0; i < arr.length; i++) {
-                       char c = arr[i];
-                       invalid: for (char invalid : INVALID_NAME_CHARACTERS) {
-                               if (c == invalid) {
-                                       arr[i] = replacement;
-                                       modified = true;
-                                       break invalid;
-                               }
-                       }
-               }
-               if (modified)
-                       return new String(arr);
-               else
-                       // do not create new object if unnecessary
-                       return name;
-       }
-
-       /**
-        * Removes forbidden characters from a path, replacing them with '_'
-        * 
-        * @deprecated use {@link #replaceInvalidChars(String)} instead
-        */
-       public static String removeForbiddenCharacters(String str) {
-               return str.replace('[', '_').replace(']', '_').replace('/', '_')
-                               .replace('*', '_');
-
-       }
-
-       /** Cleanly disposes a {@link Binary} even if it is null. */
-       public static void closeQuietly(Binary binary) {
-               if (binary == null)
-                       return;
-               binary.dispose();
-       }
-
-       /** Retrieve a {@link Binary} as a byte array */
-       public static byte[] getBinaryAsBytes(Property property) {
-               ByteArrayOutputStream out = new ByteArrayOutputStream();
-               InputStream in = null;
-               Binary binary = null;
-               try {
-                       binary = property.getBinary();
-                       in = binary.getStream();
-                       IOUtils.copy(in, out);
-                       return out.toByteArray();
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot read binary " + property
-                                       + " as bytes", e);
-               } finally {
-                       IOUtils.closeQuietly(out);
-                       IOUtils.closeQuietly(in);
-                       closeQuietly(binary);
-               }
-       }
-
-       /** Writes a {@link Binary} from a byte array */
-       public static void setBinaryAsBytes(Node node, String property, byte[] bytes) {
-               InputStream in = null;
-               Binary binary = null;
-               try {
-                       in = new ByteArrayInputStream(bytes);
-                       binary = node.getSession().getValueFactory().createBinary(in);
-                       node.setProperty(property, binary);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot read binary " + property
-                                       + " as bytes", e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-                       closeQuietly(binary);
-               }
-       }
-
-       /**
-        * Creates depth from a string (typically a username) by adding levels based
-        * on its first characters: "aBcD",2 => a/aB
-        */
-       public static String firstCharsToPath(String str, Integer nbrOfChars) {
-               if (str.length() < nbrOfChars)
-                       throw new ArgeoException("String " + str
-                                       + " length must be greater or equal than " + nbrOfChars);
-               StringBuffer path = new StringBuffer("");
-               StringBuffer curr = new StringBuffer("");
-               for (int i = 0; i < nbrOfChars; i++) {
-                       curr.append(str.charAt(i));
-                       path.append(curr);
-                       if (i < nbrOfChars - 1)
-                               path.append('/');
-               }
-               return path.toString();
-       }
-
-       /**
-        * Discards the current changes in the session attached to this node. To be
-        * used typically in a catch block.
-        * 
-        * @see #discardQuietly(Session)
-        */
-       public static void discardUnderlyingSessionQuietly(Node node) {
-               try {
-                       discardQuietly(node.getSession());
-               } catch (RepositoryException e) {
-                       log.warn("Cannot quietly discard session of node " + node + ": "
-                                       + e.getMessage());
-               }
-       }
-
-       /**
-        * Discards the current changes in a session by calling
-        * {@link Session#refresh(boolean)} with <code>false</code>, only logging
-        * potential errors when doing so. To be used typically in a catch block.
-        */
-       public static void discardQuietly(Session session) {
-               try {
-                       if (session != null)
-                               session.refresh(false);
-               } catch (RepositoryException e) {
-                       log.warn("Cannot quietly discard session " + session + ": "
-                                       + e.getMessage());
-               }
-       }
-
-       /**
-        * Login to a workspace with implicit credentials, creates the workspace
-        * with these credentials if it does not already exist.
-        */
-       public static Session loginOrCreateWorkspace(Repository repository,
-                       String workspaceName) throws RepositoryException {
-               Session workspaceSession = null;
-               Session defaultSession = null;
-               try {
-                       try {
-                               workspaceSession = repository.login(workspaceName);
-                       } catch (NoSuchWorkspaceException e) {
-                               // try to create workspace
-                               defaultSession = repository.login();
-                               defaultSession.getWorkspace().createWorkspace(workspaceName);
-                               workspaceSession = repository.login(workspaceName);
-                       }
-                       return workspaceSession;
-               } finally {
-                       logoutQuietly(defaultSession);
-               }
-       }
-
-       /** Logs out the session, not throwing any exception, even if it is null. */
-       public static void logoutQuietly(Session session) {
-               try {
-                       if (session != null)
-                               if (session.isLive())
-                                       session.logout();
-               } catch (Exception e) {
-                       // silent
-               }
-       }
-
-       /**
-        * Convenient method to add a listener. uuids passed as null, deep=true,
-        * local=true, only one node type
-        */
-       public static void addListener(Session session, EventListener listener,
-                       int eventTypes, String basePath, String nodeType) {
-               try {
-                       session.getWorkspace()
-                                       .getObservationManager()
-                                       .addEventListener(
-                                                       listener,
-                                                       eventTypes,
-                                                       basePath,
-                                                       true,
-                                                       null,
-                                                       nodeType == null ? null : new String[] { nodeType },
-                                                       true);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot add JCR listener " + listener
-                                       + " to session " + session, e);
-               }
-       }
-
-       /** Removes a listener without throwing exception */
-       public static void removeListenerQuietly(Session session,
-                       EventListener listener) {
-               if (session == null || !session.isLive())
-                       return;
-               try {
-                       session.getWorkspace().getObservationManager()
-                                       .removeEventListener(listener);
-               } catch (RepositoryException e) {
-                       // silent
-               }
-       }
-
-       /**
-        * Quietly unregisters an {@link EventListener} from the udnerlying
-        * workspace of this node.
-        */
-       public static void unregisterQuietly(Node node, EventListener eventListener) {
-               try {
-                       unregisterQuietly(node.getSession().getWorkspace(), eventListener);
-               } catch (RepositoryException e) {
-                       // silent
-                       if (log.isTraceEnabled())
-                               log.trace("Could not unregister event listener "
-                                               + eventListener);
-               }
-       }
-
-       /** Quietly unregisters an {@link EventListener} from this workspace */
-       public static void unregisterQuietly(Workspace workspace,
-                       EventListener eventListener) {
-               if (eventListener == null)
-                       return;
-               try {
-                       workspace.getObservationManager()
-                                       .removeEventListener(eventListener);
-               } catch (RepositoryException e) {
-                       // silent
-                       if (log.isTraceEnabled())
-                               log.trace("Could not unregister event listener "
-                                               + eventListener);
-               }
-       }
-
-       /**
-        * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it
-        * updates the {@link Property#JCR_LAST_MODIFIED} property with the current
-        * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the
-        * underlying session user id. In Jackrabbit 2.x, <a
-        * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
-        * are not automatically updated</a>, hence the need for manual update. The
-        * session is not saved.
-        */
-       public static void updateLastModified(Node node) {
-               try {
-                       if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED))
-                               node.addMixin(NodeType.MIX_LAST_MODIFIED);
-                       node.setProperty(Property.JCR_LAST_MODIFIED,
-                                       new GregorianCalendar());
-                       node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession()
-                                       .getUserID());
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot update last modified on " + node,
-                                       e);
-               }
-       }
-
-       /**
-        * Update lastModified recursively until this parent.
-        * 
-        * @param node
-        *            the node
-        * @param untilPath
-        *            the base path, null is equivalent to "/"
-        */
-       public static void updateLastModifiedAndParents(Node node, String untilPath) {
-               try {
-                       if (untilPath != null && !node.getPath().startsWith(untilPath))
-                               throw new ArgeoException(node + " is not under " + untilPath);
-                       updateLastModified(node);
-                       if (untilPath == null) {
-                               if (!node.getPath().equals("/"))
-                                       updateLastModifiedAndParents(node.getParent(), untilPath);
-                       } else {
-                               if (!node.getPath().equals(untilPath))
-                                       updateLastModifiedAndParents(node.getParent(), untilPath);
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot update lastModified from " + node
-                                       + " until " + untilPath, e);
-               }
-       }
-
-       /**
-        * Returns a String representing the short version (see <a
-        * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
-        * Notation </a> attributes grammar) of the main business attributes of this
-        * property definition
-        * 
-        * @param prop
-        */
-       public static String getPropertyDefinitionAsString(Property prop) {
-               StringBuffer sbuf = new StringBuffer();
-               try {
-                       if (prop.getDefinition().isAutoCreated())
-                               sbuf.append("a");
-                       if (prop.getDefinition().isMandatory())
-                               sbuf.append("m");
-                       if (prop.getDefinition().isProtected())
-                               sbuf.append("p");
-                       if (prop.getDefinition().isMultiple())
-                               sbuf.append("*");
-               } catch (RepositoryException re) {
-                       throw new ArgeoException(
-                                       "unexpected error while getting property definition as String",
-                                       re);
-               }
-               return sbuf.toString();
-       }
-
-       /**
-        * Estimate the sub tree size from current node. Computation is based on the
-        * Jcr {@link Property.getLength()} method. Note : it is not the exact size
-        * used on the disk by the current part of the JCR Tree.
-        */
-
-       public static long getNodeApproxSize(Node node) {
-               long curNodeSize = 0;
-               try {
-                       PropertyIterator pi = node.getProperties();
-                       while (pi.hasNext()) {
-                               Property prop = pi.nextProperty();
-                               if (prop.isMultiple()) {
-                                       int nb = prop.getLengths().length;
-                                       for (int i = 0; i < nb; i++) {
-                                               curNodeSize += (prop.getLengths()[i] > 0 ? prop
-                                                               .getLengths()[i] : 0);
-                                       }
-                               } else
-                                       curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0);
-                       }
-
-                       NodeIterator ni = node.getNodes();
-                       while (ni.hasNext())
-                               curNodeSize += getNodeApproxSize(ni.nextNode());
-                       return curNodeSize;
-               } catch (RepositoryException re) {
-                       throw new ArgeoException(
-                                       "Unexpected error while recursively determining node size.",
-                                       re);
-               }
-       }
-
-       /*
-        * SECURITY
-        */
-
-       /**
-        * Convenience method for adding a single privilege to a principal (user or
-        * role), typically jcr:all
-        */
-       public synchronized static void addPrivilege(Session session, String path,
-                       String principal, String privilege) throws RepositoryException {
-               List<Privilege> privileges = new ArrayList<Privilege>();
-               privileges.add(session.getAccessControlManager().privilegeFromName(
-                               privilege));
-               addPrivileges(session, path, new SimplePrincipal(principal), privileges);
-       }
-
-       /**
-        * Add privileges on a path to a {@link Principal}. The path must already
-        * exist. Session is saved. Synchronized to prevent concurrent modifications
-        * of the same node.
-        */
-       public synchronized static Boolean addPrivileges(Session session,
-                       String path, Principal principal, List<Privilege> privs)
-                       throws RepositoryException {
-               // make sure the session is in line with the persisted state
-               session.refresh(false);
-               AccessControlManager acm = session.getAccessControlManager();
-               AccessControlList acl = getAccessControlList(acm, path);
-
-               accessControlEntries: for (AccessControlEntry ace : acl
-                               .getAccessControlEntries()) {
-                       Principal currentPrincipal = ace.getPrincipal();
-                       if (currentPrincipal.getName().equals(principal.getName())) {
-                               Privilege[] currentPrivileges = ace.getPrivileges();
-                               if (currentPrivileges.length != privs.size())
-                                       break accessControlEntries;
-                               for (int i = 0; i < currentPrivileges.length; i++) {
-                                       Privilege currP = currentPrivileges[i];
-                                       Privilege p = privs.get(i);
-                                       if (!currP.getName().equals(p.getName())) {
-                                               break accessControlEntries;
-                                       }
-                               }
-                               return false;
-                       }
-               }
-
-               Privilege[] privileges = privs.toArray(new Privilege[privs.size()]);
-               acl.addAccessControlEntry(principal, privileges);
-               acm.setPolicy(path, acl);
-               if (log.isDebugEnabled()) {
-                       StringBuffer privBuf = new StringBuffer();
-                       for (Privilege priv : privs)
-                               privBuf.append(priv.getName());
-                       log.debug("Added privileges " + privBuf + " to "
-                                       + principal.getName() + " on " + path + " in '"
-                                       + session.getWorkspace().getName() + "'");
-               }
-               session.refresh(true);
-               session.save();
-               return true;
-       }
-
-       /** Gets access control list for this path, throws exception if not found */
-       public synchronized static AccessControlList getAccessControlList(
-                       AccessControlManager acm, String path) throws RepositoryException {
-               // search for an access control list
-               AccessControlList acl = null;
-               AccessControlPolicyIterator policyIterator = acm
-                               .getApplicablePolicies(path);
-               if (policyIterator.hasNext()) {
-                       while (policyIterator.hasNext()) {
-                               AccessControlPolicy acp = policyIterator
-                                               .nextAccessControlPolicy();
-                               if (acp instanceof AccessControlList)
-                                       acl = ((AccessControlList) acp);
-                       }
-               } else {
-                       AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
-                       for (AccessControlPolicy acp : existingPolicies) {
-                               if (acp instanceof AccessControlList)
-                                       acl = ((AccessControlList) acp);
-                       }
-               }
-               if (acl != null)
-                       return acl;
-               else
-                       throw new ArgeoException("ACL not found at " + path);
-       }
-
-       /** Clear authorizations for a user at this path */
-       public synchronized static void clearAccessControList(Session session,
-                       String path, String username) throws RepositoryException {
-               AccessControlManager acm = session.getAccessControlManager();
-               AccessControlList acl = getAccessControlList(acm, path);
-               for (AccessControlEntry ace : acl.getAccessControlEntries()) {
-                       if (ace.getPrincipal().getName().equals(username)) {
-                               acl.removeAccessControlEntry(ace);
-                       }
-               }
-       }
-
-       /*
-        * FILES UTILITIES
-        */
-       /**
-        * Creates the nodes making the path as {@link NodeType#NT_FOLDER}
-        */
-       public static Node mkfolders(Session session, String path) {
-               return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER,
-                               false);
-       }
-
-       /**
-        * Copy only nt:folder and nt:file, without their additional types and
-        * properties.
-        * 
-        * @param recursive
-        *            if true copies folders as well, otherwise only first level
-        *            files
-        * @return how many files were copied
-        */
-       @SuppressWarnings("resource")
-       public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive,
-                       ArgeoMonitor monitor) {
-               long count = 0l;
-
-               Binary binary = null;
-               InputStream in = null;
-               try {
-                       NodeIterator fromChildren = fromNode.getNodes();
-                       while (fromChildren.hasNext()) {
-                               if (monitor != null && monitor.isCanceled())
-                                       throw new ArgeoException(
-                                                       "Copy cancelled before it was completed");
-
-                               Node fromChild = fromChildren.nextNode();
-                               String fileName = fromChild.getName();
-                               if (fromChild.isNodeType(NodeType.NT_FILE)) {
-                                       if (monitor != null)
-                                               monitor.subTask("Copy " + fileName);
-                                       binary = fromChild.getNode(Node.JCR_CONTENT)
-                                                       .getProperty(Property.JCR_DATA).getBinary();
-                                       in = binary.getStream();
-                                       copyStreamAsFile(toNode, fileName, in);
-                                       IOUtils.closeQuietly(in);
-                                       closeQuietly(binary);
-
-                                       // save session
-                                       toNode.getSession().save();
-                                       count++;
-
-                                       if (log.isDebugEnabled())
-                                               log.debug("Copied file " + fromChild.getPath());
-                                       if (monitor != null)
-                                               monitor.worked(1);
-                               } else if (fromChild.isNodeType(NodeType.NT_FOLDER)
-                                               && recursive) {
-                                       Node toChildFolder;
-                                       if (toNode.hasNode(fileName)) {
-                                               toChildFolder = toNode.getNode(fileName);
-                                               if (!toChildFolder.isNodeType(NodeType.NT_FOLDER))
-                                                       throw new ArgeoException(toChildFolder
-                                                                       + " is not of type nt:folder");
-                                       } else {
-                                               toChildFolder = toNode.addNode(fileName,
-                                                               NodeType.NT_FOLDER);
-
-                                               // save session
-                                               toNode.getSession().save();
-                                       }
-                                       count = count
-                                                       + copyFiles(fromChild, toChildFolder, recursive,
-                                                                       monitor);
-                               }
-                       }
-                       return count;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot copy files between " + fromNode
-                                       + " and " + toNode);
-               } finally {
-                       // in case there was an exception
-                       IOUtils.closeQuietly(in);
-                       closeQuietly(binary);
-               }
-       }
-
-       /**
-        * Iteratively count all file nodes in subtree, inefficient but can be
-        * useful when query are poorly supported, such as in remoting.
-        */
-       public static Long countFiles(Node node) {
-               Long localCount = 0l;
-               try {
-                       for (NodeIterator nit = node.getNodes(); nit.hasNext();) {
-                               Node child = nit.nextNode();
-                               if (child.isNodeType(NodeType.NT_FOLDER))
-                                       localCount = localCount + countFiles(child);
-                               else if (child.isNodeType(NodeType.NT_FILE))
-                                       localCount = localCount + 1;
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot count all children of " + node);
-               }
-               return localCount;
-       }
-
-       /**
-        * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session
-        * is NOT saved.
-        * 
-        * @return the created file node
-        */
-       public static Node copyFile(Node folderNode, File file) {
-               InputStream in = null;
-               try {
-                       in = new FileInputStream(file);
-                       return copyStreamAsFile(folderNode, file.getName(), in);
-               } catch (IOException e) {
-                       throw new ArgeoException("Cannot copy file " + file + " under "
-                                       + folderNode, e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-               }
-       }
-
-       /** Copy bytes as an nt:file */
-       public static Node copyBytesAsFile(Node folderNode, String fileName,
-                       byte[] bytes) {
-               InputStream in = null;
-               try {
-                       in = new ByteArrayInputStream(bytes);
-                       return copyStreamAsFile(folderNode, fileName, in);
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot copy file " + fileName + " under "
-                                       + folderNode, e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-               }
-       }
-
-       /**
-        * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session
-        * is NOT saved.
-        * 
-        * @return the created file node
-        */
-       public static Node copyStreamAsFile(Node folderNode, String fileName,
-                       InputStream in) {
-               Binary binary = null;
-               try {
-                       Node fileNode;
-                       Node contentNode;
-                       if (folderNode.hasNode(fileName)) {
-                               fileNode = folderNode.getNode(fileName);
-                               if (!fileNode.isNodeType(NodeType.NT_FILE))
-                                       throw new ArgeoException(fileNode
-                                                       + " is not of type nt:file");
-                               // we assume that the content node is already there
-                               contentNode = fileNode.getNode(Node.JCR_CONTENT);
-                       } else {
-                               fileNode = folderNode.addNode(fileName, NodeType.NT_FILE);
-                               contentNode = fileNode.addNode(Node.JCR_CONTENT,
-                                               NodeType.NT_RESOURCE);
-                       }
-                       binary = contentNode.getSession().getValueFactory()
-                                       .createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-                       return fileNode;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot create file node " + fileName
-                                       + " under " + folderNode, e);
-               } finally {
-                       closeQuietly(binary);
-               }
-       }
-
-       /** Computes the checksum of an nt:file */
-       public static String checksumFile(Node fileNode, String algorithm) {
-               Binary data = null;
-               InputStream in = null;
-               try {
-                       data = fileNode.getNode(Node.JCR_CONTENT)
-                                       .getProperty(Property.JCR_DATA).getBinary();
-                       in = data.getStream();
-                       return DigestUtils.digest(algorithm, in);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot checksum file " + fileNode, e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-                       closeQuietly(data);
-               }
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/MaintainedRepository.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/MaintainedRepository.java
deleted file mode 100644 (file)
index 702d47a..0000000
+++ /dev/null
@@ -1,8 +0,0 @@
-package org.argeo.jcr;
-
-import javax.jcr.Repository;
-
-/** Abstracts maintenance operations on a {@link Repository} */
-public interface MaintainedRepository extends Repository {
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/NodeMapper.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/NodeMapper.java
deleted file mode 100644 (file)
index af792c3..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.Session;
-
-public interface NodeMapper {
-       public Object load(Node node);
-
-       public void update(Node node, Object obj);
-
-       public Node save(Session session, String path, Object obj);
-       
-       public void setNodeMapperProvider(NodeMapperProvider nmp);
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/NodeMapperProvider.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/NodeMapperProvider.java
deleted file mode 100644 (file)
index 07e623b..0000000
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Node;
-
-/** Provides a node mapper relevant for this node. */
-public interface NodeMapperProvider {
-
-       /** 
-        * Node Mapper is chosen regarding the Jcr path of the node parameter 
-        * @param Node node
-        * @return the node mapper or null if no relevant node mapper can be found. */
-       public NodeMapper findNodeMapper(Node node);
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/PropertyDiff.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/PropertyDiff.java
deleted file mode 100644 (file)
index bdd316f..0000000
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Value;
-
-import org.argeo.ArgeoException;
-
-/** The result of the comparison of two JCR properties. */
-public class PropertyDiff {
-       public final static Integer MODIFIED = 0;
-       public final static Integer ADDED = 1;
-       public final static Integer REMOVED = 2;
-
-       private final Integer type;
-       private final String relPath;
-       private final Value referenceValue;
-       private final Value newValue;
-
-       public PropertyDiff(Integer type, String relPath, Value referenceValue,
-                       Value newValue) {
-               super();
-
-               if (type == MODIFIED) {
-                       if (referenceValue == null || newValue == null)
-                               throw new ArgeoException(
-                                               "Reference and new values must be specified.");
-               } else if (type == ADDED) {
-                       if (referenceValue != null || newValue == null)
-                               throw new ArgeoException(
-                                               "New value and only it must be specified.");
-               } else if (type == REMOVED) {
-                       if (referenceValue == null || newValue != null)
-                               throw new ArgeoException(
-                                               "Reference value and only it must be specified.");
-               } else {
-                       throw new ArgeoException("Unkown diff type " + type);
-               }
-
-               if (relPath == null)
-                       throw new ArgeoException("Relative path must be specified");
-
-               this.type = type;
-               this.relPath = relPath;
-               this.referenceValue = referenceValue;
-               this.newValue = newValue;
-       }
-
-       public Integer getType() {
-               return type;
-       }
-
-       public String getRelPath() {
-               return relPath;
-       }
-
-       public Value getReferenceValue() {
-               return referenceValue;
-       }
-
-       public Value getNewValue() {
-               return newValue;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/RepositoryRegister.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/RepositoryRegister.java
deleted file mode 100644 (file)
index 2e3d455..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryFactory;
-
-/** Allows to register repositories by name. */
-public interface RepositoryRegister extends RepositoryFactory {
-       /**
-        * The registered {@link Repository} as a read-only map. Note that this
-        * method should be called for each access in order to be sure to be up to
-        * date in case repositories have registered/unregistered
-        */
-       public Map<String, Repository> getRepositories();
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ThreadBoundJcrSessionFactory.java
deleted file mode 100644 (file)
index 193f22c..0000000
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.lang.reflect.InvocationHandler;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.LoginException;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-
-/** Proxy JCR sessions and attach them to calling threads. */
-public abstract class ThreadBoundJcrSessionFactory {
-       private final static Log log = LogFactory
-                       .getLog(ThreadBoundJcrSessionFactory.class);
-
-       private Repository repository;
-       /** can be injected as list, only used if repository is null */
-       private List<Repository> repositories;
-
-       private ThreadLocal<Session> session = new ThreadLocal<Session>();
-       private final Session proxiedSession;
-       /** If workspace is null, default will be used. */
-       private String workspace = null;
-
-       private String defaultUsername = "demo";
-       private String defaultPassword = "demo";
-       private Boolean forceDefaultCredentials = false;
-
-       private boolean active = true;
-
-       // monitoring
-       private final List<Thread> threads = Collections
-                       .synchronizedList(new ArrayList<Thread>());
-       private final Map<Long, Session> activeSessions = Collections
-                       .synchronizedMap(new HashMap<Long, Session>());
-       private MonitoringThread monitoringThread;
-
-       public ThreadBoundJcrSessionFactory() {
-               Class<?>[] interfaces = { Session.class };
-               proxiedSession = (Session) Proxy.newProxyInstance(
-                               ThreadBoundJcrSessionFactory.class.getClassLoader(),
-                               interfaces, new JcrSessionInvocationHandler());
-       }
-
-       /** Logs in to the repository using various strategies. */
-       protected synchronized Session login() {
-               if (!isActive())
-                       throw new ArgeoException("Thread bound session factory inactive");
-
-               // discard session previously attached to this thread
-               Thread thread = Thread.currentThread();
-               if (activeSessions.containsKey(thread.getId())) {
-                       Session oldSession = activeSessions.remove(thread.getId());
-                       oldSession.logout();
-                       session.remove();
-               }
-
-               Session newSession = null;
-               // first try to login without credentials, assuming the underlying login
-               // module will have dealt with authentication (typically using Spring
-               // Security)
-               if (!forceDefaultCredentials)
-                       try {
-                               newSession = repository().login(workspace);
-                       } catch (LoginException e1) {
-                               log.warn("Cannot login without credentials: " + e1.getMessage());
-                               // invalid credentials, go to the next step
-                       } catch (RepositoryException e1) {
-                               // other kind of exception, fail
-                               throw new ArgeoException("Cannot log in to repository", e1);
-                       }
-
-               // log using default username / password (useful for testing purposes)
-               if (newSession == null)
-                       try {
-                               SimpleCredentials sc = new SimpleCredentials(defaultUsername,
-                                               defaultPassword.toCharArray());
-                               newSession = repository().login(sc, workspace);
-                       } catch (RepositoryException e) {
-                               throw new ArgeoException("Cannot log in to repository", e);
-                       }
-
-               session.set(newSession);
-               // Log and monitor new session
-               if (log.isTraceEnabled())
-                       log.trace("Logged in to JCR session " + newSession + "; userId="
-                                       + newSession.getUserID());
-
-               // monitoring
-               activeSessions.put(thread.getId(), newSession);
-               threads.add(thread);
-               return newSession;
-       }
-
-       public Object getObject() {
-               return proxiedSession;
-       }
-
-       public void init() throws Exception {
-               monitoringThread = new MonitoringThread();
-               monitoringThread.start();
-       }
-
-       public synchronized void dispose() throws Exception {
-               if (activeSessions.size() == 0)
-                       return;
-
-               if (log.isTraceEnabled())
-                       log.trace("Cleaning up " + activeSessions.size()
-                                       + " active JCR sessions...");
-
-               deactivate();
-               for (Session sess : activeSessions.values()) {
-                       JcrUtils.logoutQuietly(sess);
-               }
-               activeSessions.clear();
-       }
-
-       protected Boolean isActive() {
-               return active;
-       }
-
-       protected synchronized void deactivate() {
-               active = false;
-               notifyAll();
-       }
-
-       protected synchronized void removeSession(Thread thread) {
-               if (!isActive())
-                       return;
-               activeSessions.remove(thread.getId());
-               threads.remove(thread);
-       }
-
-       protected synchronized void cleanDeadThreads() {
-               if (!isActive())
-                       return;
-               Iterator<Thread> it = threads.iterator();
-               while (it.hasNext()) {
-                       Thread thread = it.next();
-                       if (!thread.isAlive() && isActive()) {
-                               if (activeSessions.containsKey(thread.getId())) {
-                                       Session session = activeSessions.get(thread.getId());
-                                       activeSessions.remove(thread.getId());
-                                       session.logout();
-                                       if (log.isTraceEnabled())
-                                               log.trace("Cleaned up JCR session (userID="
-                                                               + session.getUserID() + ") from dead thread "
-                                                               + thread.getId());
-                               }
-                               it.remove();
-                       }
-               }
-               try {
-                       wait(1000);
-               } catch (InterruptedException e) {
-                       // silent
-               }
-       }
-
-       public Class<? extends Session> getObjectType() {
-               return Session.class;
-       }
-
-       public boolean isSingleton() {
-               return true;
-       }
-
-       /**
-        * Called before a method is actually called, allowing to check the session
-        * or re-login it (e.g. if authentication has changed). The default
-        * implementation returns the session.
-        */
-       protected Session preCall(Session session) {
-               return session;
-       }
-
-       protected Repository repository() {
-               if (repository != null)
-                       return repository;
-               if (repositories != null) {
-                       // hardened for OSGi dynamic services
-                       Iterator<Repository> it = repositories.iterator();
-                       if (it.hasNext())
-                               return it.next();
-               }
-               throw new ArgeoException("No repository injected");
-       }
-
-       // /** Useful for declarative registration of OSGi services (blueprint) */
-       // public void register(Repository repository, Map<?, ?> params) {
-       // this.repository = repository;
-       // }
-       //
-       // /** Useful for declarative registration of OSGi services (blueprint) */
-       // public void unregister(Repository repository, Map<?, ?> params) {
-       // this.repository = null;
-       // }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setRepositories(List<Repository> repositories) {
-               this.repositories = repositories;
-       }
-
-       public void setDefaultUsername(String defaultUsername) {
-               this.defaultUsername = defaultUsername;
-       }
-
-       public void setDefaultPassword(String defaultPassword) {
-               this.defaultPassword = defaultPassword;
-       }
-
-       public void setForceDefaultCredentials(Boolean forceDefaultCredentials) {
-               this.forceDefaultCredentials = forceDefaultCredentials;
-       }
-
-       public void setWorkspace(String workspace) {
-               this.workspace = workspace;
-       }
-
-       protected class JcrSessionInvocationHandler implements InvocationHandler {
-
-               public Object invoke(Object proxy, Method method, Object[] args)
-                               throws Throwable, RepositoryException {
-                       Session threadSession = session.get();
-                       if (threadSession == null) {
-                               if ("logout".equals(method.getName()))// no need to login
-                                       return Void.TYPE;
-                               else if ("toString".equals(method.getName()))// maybe logging
-                                       return "Uninitialized Argeo thread bound JCR session";
-                               threadSession = login();
-                       }
-
-                       preCall(threadSession);
-                       Object ret;
-                       try {
-                               ret = method.invoke(threadSession, args);
-                       } catch (InvocationTargetException e) {
-                               Throwable cause = e.getCause();
-                               if (cause instanceof RepositoryException)
-                                       throw (RepositoryException) cause;
-                               else
-                                       throw cause;
-                       }
-                       if ("logout".equals(method.getName())) {
-                               session.remove();
-                               Thread thread = Thread.currentThread();
-                               removeSession(thread);
-                               if (log.isTraceEnabled())
-                                       log.trace("Logged out JCR session (userId="
-                                                       + threadSession.getUserID() + ") on thread "
-                                                       + thread.getId());
-                       }
-                       return ret;
-               }
-       }
-
-       /** Monitors registered thread in order to clean up dead ones. */
-       private class MonitoringThread extends Thread {
-
-               public MonitoringThread() {
-                       super("ThreadBound JCR Session Monitor");
-               }
-
-               @Override
-               public void run() {
-                       while (isActive()) {
-                               cleanDeadThreads();
-                       }
-               }
-
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/UserJcrUtils.java
deleted file mode 100644 (file)
index 1758802..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Node;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.query.Query;
-import javax.jcr.query.qom.Constraint;
-import javax.jcr.query.qom.DynamicOperand;
-import javax.jcr.query.qom.QueryObjectModelFactory;
-import javax.jcr.query.qom.Selector;
-import javax.jcr.query.qom.StaticOperand;
-
-import org.argeo.ArgeoException;
-
-/** Utilities related to the user home and properties based on Argeo JCR model. */
-public class UserJcrUtils {
-       /** The home base path. Not yet configurable */
-       public final static String DEFAULT_HOME_BASE_PATH = "/home";
-
-       /**
-        * Returns the home node of the user or null if none was found.
-        * 
-        * @param session
-        *            the session to use in order to perform the search, this can be
-        *            a session with a different user ID than the one searched,
-        *            typically when a system or admin session is used.
-        * @param username
-        *            the username of the user
-        */
-       public static Node getUserHome(Session session, String username) {
-               try {
-                       // String homePath = UserJcrUtils.getUserHomePath(username);
-                       // return session.itemExists(homePath) ? session.getNode(homePath)
-                       // : null;
-                       // kept for example of QOM queries
-                       QueryObjectModelFactory qomf = session.getWorkspace()
-                                       .getQueryManager().getQOMFactory();
-                       Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME,
-                                       "userHome");
-                       DynamicOperand userIdDop = qomf.propertyValue(
-                                       userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID);
-                       StaticOperand userIdSop = qomf.literal(session.getValueFactory()
-                                       .createValue(username));
-                       Constraint constraint = qomf.comparison(userIdDop,
-                                       QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop);
-                       Query query = qomf.createQuery(userHomeSel, constraint, null, null);
-                       return JcrUtils.querySingleNode(query);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot find home for user " + username, e);
-               }
-       }
-
-       public static Node getUserProfile(Session session, String username) {
-               try {
-                       QueryObjectModelFactory qomf = session.getWorkspace()
-                                       .getQueryManager().getQOMFactory();
-                       Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE,
-                                       "userProfile");
-                       DynamicOperand userIdDop = qomf.propertyValue(
-                                       userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID);
-                       StaticOperand userIdSop = qomf.literal(session.getValueFactory()
-                                       .createValue(username));
-                       Constraint constraint = qomf.comparison(userIdDop,
-                                       QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop);
-                       Query query = qomf.createQuery(userHomeSel, constraint, null, null);
-                       return JcrUtils.querySingleNode(query);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException(
-                                       "Cannot find profile for user " + username, e);
-               }
-       }
-
-       /** Returns the home node of the session user or null if none was found. */
-       public static Node getUserHome(Session session) {
-               String userID = session.getUserID();
-               return getUserHome(session, userID);
-       }
-
-       private UserJcrUtils() {
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/VersionDiff.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/VersionDiff.java
deleted file mode 100644 (file)
index e6ae913..0000000
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.Calendar;
-import java.util.Map;
-
-/**
- * Generic Object that enables the creation of history reports based on a JCR
- * versionable node. userId and creation date are added to the map of
- * PropertyDiff.
- * 
- * These two fields might be null
- * 
- */
-public class VersionDiff {
-
-       private String userId;
-       private Map<String, PropertyDiff> diffs;
-       private Calendar updateTime;
-
-       public VersionDiff(String userId, Calendar updateTime,
-                       Map<String, PropertyDiff> diffs) {
-               this.userId = userId;
-               this.updateTime = updateTime;
-               this.diffs = diffs;
-       }
-
-       public String getUserId() {
-               return userId;
-       }
-
-       public Map<String, PropertyDiff> getDiffs() {
-               return diffs;
-       }
-
-       public Calendar getUpdateTime() {
-               return updateTime;
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/proxy/AbstractUrlProxy.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/proxy/AbstractUrlProxy.java
deleted file mode 100644 (file)
index 8a66f31..0000000
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.proxy;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.net.URL;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.nodetype.NodeType;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-
-/** Base class for URL based proxys. */
-public abstract class AbstractUrlProxy implements ResourceProxy {
-       private final static Log log = LogFactory.getLog(AbstractUrlProxy.class);
-
-       private Repository jcrRepository;
-       private Session jcrAdminSession;
-       private String proxyWorkspace = "proxy";
-
-       protected abstract Node retrieve(Session session, String path);
-
-       void init() {
-               try {
-                       jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository,
-                                       proxyWorkspace);
-                       beforeInitSessionSave(jcrAdminSession);
-                       if (jcrAdminSession.hasPendingChanges())
-                               jcrAdminSession.save();
-               } catch (Exception e) {
-                       JcrUtils.discardQuietly(jcrAdminSession);
-                       throw new ArgeoException("Cannot initialize Maven proxy", e);
-               }
-       }
-
-       /**
-        * Called before the (admin) session is saved at the end of the
-        * initialization. Does nothing by default, to be overridden.
-        */
-       protected void beforeInitSessionSave(Session session)
-                       throws RepositoryException {
-       }
-
-       void destroy() {
-               JcrUtils.logoutQuietly(jcrAdminSession);
-       }
-
-       /**
-        * Called before the (admin) session is logged out when resources are
-        * released. Does nothing by default, to be overridden.
-        */
-       protected void beforeDestroySessionLogout() throws RepositoryException {
-       }
-
-       public Node proxy(String path) {
-               // we open a JCR session with client credentials in order not to use the
-               // admin session in multiple thread or make it a bottleneck.
-               Node nodeAdmin = null;
-               Node nodeClient = null;
-               Session clientSession = null;
-               try {
-                       clientSession = jcrRepository.login(proxyWorkspace);
-                       if (!clientSession.itemExists(path)
-                                       || shouldUpdate(clientSession, path)) {
-                               nodeAdmin = retrieveAndSave(path);
-                               if (nodeAdmin != null)
-                                       nodeClient = clientSession.getNode(path);
-                       } else
-                               nodeClient = clientSession.getNode(path);
-                       return nodeClient;
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot proxy " + path, e);
-               } finally {
-                       if (nodeClient == null)
-                               JcrUtils.logoutQuietly(clientSession);
-               }
-       }
-
-       protected synchronized Node retrieveAndSave(String path) {
-               try {
-                       Node node = retrieve(jcrAdminSession, path);
-                       if (node == null)
-                               return null;
-                       jcrAdminSession.save();
-                       return node;
-               } catch (RepositoryException e) {
-                       JcrUtils.discardQuietly(jcrAdminSession);
-                       throw new ArgeoException("Cannot retrieve and save " + path, e);
-               } finally {
-                       notifyAll();
-               }
-       }
-
-       /** Session is not saved */
-       protected synchronized Node proxyUrl(Session session, String remoteUrl,
-                       String path) throws RepositoryException {
-               Node node = null;
-               if (session.itemExists(path)) {
-                       // throw new ArgeoException("Node " + path + " already exists");
-               }
-               InputStream in = null;
-               try {
-                       URL u = new URL(remoteUrl);
-                       in = u.openStream();
-                       node = importFile(session, path, in);
-               } catch (IOException e) {
-                       if (log.isDebugEnabled()) {
-                               log.debug("Cannot read " + remoteUrl + ", skipping... "
-                                               + e.getMessage());
-                               // log.trace("Cannot read because of ", e);
-                       }
-                       JcrUtils.discardQuietly(session);
-               } finally {
-                       IOUtils.closeQuietly(in);
-               }
-               return node;
-       }
-
-       protected synchronized Node importFile(Session session, String path,
-                       InputStream in) throws RepositoryException {
-               Binary binary = null;
-               try {
-                       Node content = null;
-                       Node node = null;
-                       if (!session.itemExists(path)) {
-                               node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE,
-                                               NodeType.NT_FOLDER, false);
-                               content = node.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE);
-                       } else {
-                               node = session.getNode(path);
-                               content = node.getNode(Node.JCR_CONTENT);
-                       }
-                       binary = session.getValueFactory().createBinary(in);
-                       content.setProperty(Property.JCR_DATA, binary);
-                       JcrUtils.updateLastModifiedAndParents(node, null);
-                       return node;
-               } finally {
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-
-       /** Whether the file should be updated. */
-       protected Boolean shouldUpdate(Session clientSession, String nodePath) {
-               return false;
-       }
-
-       public void setJcrRepository(Repository jcrRepository) {
-               this.jcrRepository = jcrRepository;
-       }
-
-       public void setProxyWorkspace(String localWorkspace) {
-               this.proxyWorkspace = localWorkspace;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/proxy/ResourceProxy.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/proxy/ResourceProxy.java
deleted file mode 100644 (file)
index b4fb332..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.proxy;
-
-import javax.jcr.Node;
-
-/** A proxy which nows how to resolve and synchronize relative URLs */
-public interface ResourceProxy {
-       /**
-        * Proxy the file referenced by this relative path in the underlying
-        * repository. A new session is created by each call, so the underlying
-        * session of the returned node must be closed by the caller.
-        * 
-        * @return the proxied Node, <code>null</code> if the resource was not found
-        *         (e.g. HTTP 404)
-        */
-       public Node proxy(String relativePath);
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/security/JcrAuthorizations.java
deleted file mode 100644 (file)
index 491f8a6..0000000
+++ /dev/null
@@ -1,223 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.security;
-
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.Privilege;
-
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.security.SimplePrincipal;
-
-/** Apply authorizations to a JCR repository. */
-public class JcrAuthorizations implements Runnable {
-       // private final static Log log =
-       // LogFactory.getLog(JcrAuthorizations.class);
-
-       private Repository repository;
-       private String workspace = null;
-
-       private String securityWorkspace = "security";
-
-       /**
-        * key := privilege1,privilege2/path/to/node<br/>
-        * value := group1,group2,user1
-        */
-       private Map<String, String> principalPrivileges = new HashMap<String, String>();
-
-       public void run() {
-               String currentWorkspace = workspace;
-               Session session = null;
-               try {
-                       if (workspace != null && workspace.equals("*")) {
-                               session = repository.login();
-                               String[] workspaces = session.getWorkspace()
-                                               .getAccessibleWorkspaceNames();
-                               JcrUtils.logoutQuietly(session);
-                               for (String wksp : workspaces) {
-                                       currentWorkspace = wksp;
-                                       if (currentWorkspace.equals(securityWorkspace))
-                                               continue;
-                                       session = repository.login(currentWorkspace);
-                                       initAuthorizations(session);
-                                       JcrUtils.logoutQuietly(session);
-                               }
-                       } else {
-                               session = repository.login(workspace);
-                               initAuthorizations(session);
-                       }
-               } catch (Exception e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new ArgeoException(
-                                       "Cannot set authorizations " + principalPrivileges
-                                                       + " on workspace " + currentWorkspace, e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       protected void processWorkspace(String workspace) {
-               Session session = null;
-               try {
-                       session = repository.login(workspace);
-                       initAuthorizations(session);
-               } catch (Exception e) {
-                       JcrUtils.discardQuietly(session);
-                       throw new ArgeoException("Cannot set authorizations "
-                                       + principalPrivileges + " on repository " + repository, e);
-               } finally {
-                       JcrUtils.logoutQuietly(session);
-               }
-       }
-
-       /** @deprecated call {@link #run()} instead. */
-       @Deprecated
-       public void init() {
-               run();
-       }
-
-       protected void initAuthorizations(Session session)
-                       throws RepositoryException {
-               AccessControlManager acm = session.getAccessControlManager();
-
-               for (String privileges : principalPrivileges.keySet()) {
-                       String path = null;
-                       int slashIndex = privileges.indexOf('/');
-                       if (slashIndex == 0) {
-                               throw new ArgeoException("Privilege " + privileges
-                                               + " badly formatted it starts with /");
-                       } else if (slashIndex > 0) {
-                               path = privileges.substring(slashIndex);
-                               privileges = privileges.substring(0, slashIndex);
-                       }
-
-                       if (path == null)
-                               path = "/";
-
-                       List<Privilege> privs = new ArrayList<Privilege>();
-                       for (String priv : privileges.split(",")) {
-                               privs.add(acm.privilegeFromName(priv));
-                       }
-
-                       String principalNames = principalPrivileges.get(privileges);
-                       for (String principalName : principalNames.split(",")) {
-                               Principal principal = getOrCreatePrincipal(session,
-                                               principalName);
-                               JcrUtils.addPrivileges(session, path, principal, privs);
-                               // if (log.isDebugEnabled()) {
-                               // StringBuffer privBuf = new StringBuffer();
-                               // for (Privilege priv : privs)
-                               // privBuf.append(priv.getName());
-                               // log.debug("Added privileges " + privBuf + " to "
-                               // + principal.getName() + " on " + path + " in '"
-                               // + session.getWorkspace().getName() + "'");
-                               // }
-                       }
-               }
-
-               // if (log.isDebugEnabled())
-               // log.debug("JCR authorizations applied on '"
-               // + session.getWorkspace().getName() + "'");
-       }
-
-       /**
-        * Returns a {@link SimplePrincipal}, does not check whether it exists since
-        * such capabilities is not provided by the standard JCR API. Can be
-        * overridden to provide smarter handling
-        */
-       protected Principal getOrCreatePrincipal(Session session,
-                       String principalName) throws RepositoryException {
-               return new SimplePrincipal(principalName);
-       }
-
-       // public static void addPrivileges(Session session, Principal principal,
-       // String path, List<Privilege> privs) throws RepositoryException {
-       // AccessControlManager acm = session.getAccessControlManager();
-       // // search for an access control list
-       // AccessControlList acl = null;
-       // AccessControlPolicyIterator policyIterator = acm
-       // .getApplicablePolicies(path);
-       // if (policyIterator.hasNext()) {
-       // while (policyIterator.hasNext()) {
-       // AccessControlPolicy acp = policyIterator
-       // .nextAccessControlPolicy();
-       // if (acp instanceof AccessControlList)
-       // acl = ((AccessControlList) acp);
-       // }
-       // } else {
-       // AccessControlPolicy[] existingPolicies = acm.getPolicies(path);
-       // for (AccessControlPolicy acp : existingPolicies) {
-       // if (acp instanceof AccessControlList)
-       // acl = ((AccessControlList) acp);
-       // }
-       // }
-       //
-       // if (acl != null) {
-       // acl.addAccessControlEntry(principal,
-       // privs.toArray(new Privilege[privs.size()]));
-       // acm.setPolicy(path, acl);
-       // session.save();
-       // if (log.isDebugEnabled()) {
-       // StringBuffer buf = new StringBuffer("");
-       // for (int i = 0; i < privs.size(); i++) {
-       // if (i != 0)
-       // buf.append(',');
-       // buf.append(privs.get(i).getName());
-       // }
-       // log.debug("Added privilege(s) '" + buf + "' to '"
-       // + principal.getName() + "' on " + path
-       // + " from workspace '"
-       // + session.getWorkspace().getName() + "'");
-       // }
-       // } else {
-       // throw new ArgeoException("Don't know how to apply  privileges "
-       // + privs + " to " + principal + " on " + path
-       // + " from workspace '" + session.getWorkspace().getName()
-       // + "'");
-       // }
-       // }
-
-       @Deprecated
-       public void setGroupPrivileges(Map<String, String> groupPrivileges) {
-               this.principalPrivileges = groupPrivileges;
-       }
-
-       public void setPrincipalPrivileges(Map<String, String> principalPrivileges) {
-               this.principalPrivileges = principalPrivileges;
-       }
-
-       public void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-
-       public void setWorkspace(String workspace) {
-               this.workspace = workspace;
-       }
-
-       public void setSecurityWorkspace(String securityWorkspace) {
-               this.securityWorkspace = securityWorkspace;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/spring/BeanNodeMapper.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/spring/BeanNodeMapper.java
deleted file mode 100644 (file)
index 9f70f5c..0000000
+++ /dev/null
@@ -1,649 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.spring;
-
-import java.beans.PropertyDescriptor;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.StringTokenizer;
-
-import javax.jcr.Binary;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyIterator;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.jcr.NodeMapper;
-import org.argeo.jcr.NodeMapperProvider;
-import org.springframework.beans.BeanWrapper;
-import org.springframework.beans.BeanWrapperImpl;
-
-public class BeanNodeMapper implements NodeMapper {
-       private final static Log log = LogFactory.getLog(BeanNodeMapper.class);
-
-       private final static String NODE_VALUE = "value";
-
-       // private String keyNode = "bean:key";
-       private String uuidProperty = "uuid";
-       private String classProperty = "class";
-
-       private Boolean versioning = false;
-       private Boolean strictUuidReference = false;
-
-       // TODO define a primaryNodeType Strategy
-       private String primaryNodeType = null;
-
-       private ClassLoader classLoader = getClass().getClassLoader();
-
-       private NodeMapperProvider nodeMapperProvider;
-
-       /**
-        * exposed method to retrieve a bean from a node
-        */
-       public Object load(Node node) {
-               try {
-                       if (nodeMapperProvider != null) {
-                               NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node);
-                               if (nodeMapper != this) {
-                                       return nodeMapper.load(node);
-                               }
-                       }
-                       return nodeToBean(node);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot load object from node " + node, e);
-               }
-       }
-
-       /** Update an existing node with an object */
-       public void update(Node node, Object obj) {
-               try {
-                       if (nodeMapperProvider != null) {
-
-                               NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node);
-                               if (nodeMapper != this) {
-                                       nodeMapper.update(node, obj);
-                               } else
-                                       beanToNode(createBeanWrapper(obj), node);
-                       } else
-                               beanToNode(createBeanWrapper(obj), node);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot update node " + node + " with "
-                                       + obj, e);
-               }
-       }
-
-       /**
-        * if no storage path is given; we use canonical path
-        * 
-        * @see this.storagePath()
-        */
-       public Node save(Session session, Object obj) {
-               return save(session, storagePath(obj), obj);
-       }
-
-       /**
-        * Create a new node to store an object. If the parentNode doesn't exist, it
-        * is created
-        * 
-        * the primaryNodeType may be initialized before
-        */
-       public Node save(Session session, String path, Object obj) {
-               try {
-                       final Node node;
-                       String parentPath = JcrUtils.parentPath(path);
-                       // find or create parent node
-                       Node parentNode;
-                       if (session.itemExists(path))
-                               parentNode = (Node) session.getItem(parentPath);
-                       else {
-                               parentNode = JcrUtils.mkdirs(session, parentPath, null, null,
-                                               versioning);
-                       }
-                       // create node
-
-                       if (primaryNodeType != null)
-                               node = parentNode.addNode(JcrUtils.lastPathElement(path),
-                                               primaryNodeType);
-                       else
-                               node = parentNode.addNode(JcrUtils.lastPathElement(path));
-
-                       // Check specific cases
-                       if (nodeMapperProvider != null) {
-                               NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node);
-                               if (nodeMapper != this) {
-                                       nodeMapper.update(node, obj);
-                                       return node;
-                               }
-                       }
-                       update(node, obj);
-                       return node;
-               } catch (ArgeoException e) {
-                       throw e;
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot save or update " + obj + " under "
-                                       + path, e);
-               }
-       }
-
-       /**
-        * Parse the FQN of a class to string with '/' delimiters Prefix the
-        * returned string with "/objects/"
-        */
-       public String storagePath(Object obj) {
-               String clss = obj.getClass().getName();
-               StringBuffer buf = new StringBuffer("/objects/");
-               StringTokenizer st = new StringTokenizer(clss, ".");
-               while (st.hasMoreTokens()) {
-                       buf.append(st.nextToken()).append('/');
-               }
-               buf.append(obj.toString());
-               return buf.toString();
-       }
-
-       @SuppressWarnings("unchecked")
-       /** 
-        * Transforms a node into an object of the class defined by classProperty Property
-        */
-       protected Object nodeToBean(Node node) throws RepositoryException {
-               if (log.isTraceEnabled())
-                       log.trace("Load     " + node);
-
-               try {
-                       String clssName = node.getProperty(classProperty).getValue()
-                                       .getString();
-
-                       BeanWrapper beanWrapper = createBeanWrapper(loadClass(clssName));
-
-                       // process properties
-                       PropertyIterator propIt = node.getProperties();
-                       props: while (propIt.hasNext()) {
-                               Property prop = propIt.nextProperty();
-                               if (!beanWrapper.isWritableProperty(prop.getName()))
-                                       continue props;
-
-                               PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(prop
-                                               .getName());
-                               Class<?> propClass = pd.getPropertyType();
-
-                               if (log.isTraceEnabled())
-                                       log.trace("Load " + prop + ", propClass=" + propClass
-                                                       + ", property descriptor=" + pd);
-
-                               // primitive list
-                               if (propClass != null && List.class.isAssignableFrom(propClass)) {
-                                       List<Object> lst = new ArrayList<Object>();
-                                       Class<?> valuesClass = classFromProperty(prop);
-                                       if (valuesClass != null)
-                                               for (Value value : prop.getValues()) {
-                                                       lst.add(asObject(value, valuesClass));
-                                               }
-                                       continue props;
-                               }
-
-                               // Case of other type of property accepted by jcr
-                               // Long, Double, String, Binary, Date, Boolean, Name
-                               Object value = asObject(prop.getValue(), pd.getPropertyType());
-                               if (value != null)
-                                       beanWrapper.setPropertyValue(prop.getName(), value);
-                       }
-
-                       // process children nodes
-                       NodeIterator nodeIt = node.getNodes();
-                       nodes: while (nodeIt.hasNext()) {
-                               Node childNode = nodeIt.nextNode();
-                               String name = childNode.getName();
-                               if (!beanWrapper.isWritableProperty(name))
-                                       continue nodes;
-
-                               PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name);
-                               Class<?> propClass = pd.getPropertyType();
-
-                               // objects list
-                               if (propClass != null && List.class.isAssignableFrom(propClass)) {
-                                       String lstClass = childNode.getProperty(classProperty)
-                                                       .getString();
-                                       List<Object> lst;
-                                       try {
-                                               lst = (List<Object>) loadClass(lstClass).newInstance();
-                                       } catch (Exception e) {
-                                               lst = new ArrayList<Object>();
-                                       }
-
-                                       if (childNode.hasNodes()) {
-                                               // Look for children nodes
-                                               NodeIterator valuesIt = childNode.getNodes();
-                                               while (valuesIt.hasNext()) {
-                                                       Node lstValueNode = valuesIt.nextNode();
-                                                       Object lstValue = nodeToBean(lstValueNode);
-                                                       lst.add(lstValue);
-                                               }
-                                       } else {
-                                               // look for a property with the same name which will
-                                               // provide
-                                               // primitives
-                                               Property childProp = childNode.getProperty(childNode
-                                                               .getName());
-                                               Class<?> valuesClass = classFromProperty(childProp);
-                                               if (valuesClass != null)
-                                                       if (childProp.getDefinition().isMultiple())
-                                                               for (Value value : childProp.getValues()) {
-                                                                       lst.add(asObject(value, valuesClass));
-                                                               }
-                                                       else
-                                                               lst.add(asObject(childProp.getValue(),
-                                                                               valuesClass));
-                                       }
-                                       beanWrapper.setPropertyValue(name, lst);
-                                       continue nodes;
-                               }
-
-                               // objects map
-                               if (propClass != null && Map.class.isAssignableFrom(propClass)) {
-                                       String mapClass = childNode.getProperty(classProperty)
-                                                       .getString();
-                                       Map<Object, Object> map;
-                                       try {
-                                               map = (Map<Object, Object>) loadClass(mapClass)
-                                                               .newInstance();
-                                       } catch (Exception e) {
-                                               map = new HashMap<Object, Object>();
-                                       }
-
-                                       // properties
-                                       PropertyIterator keysPropIt = childNode.getProperties();
-                                       keyProps: while (keysPropIt.hasNext()) {
-                                               Property keyProp = keysPropIt.nextProperty();
-                                               // FIXME: use property editor
-                                               String key = keyProp.getName();
-                                               if (classProperty.equals(key))
-                                                       continue keyProps;
-
-                                               Class<?> keyPropClass = classFromProperty(keyProp);
-                                               if (keyPropClass != null) {
-                                                       Object mapValue = asObject(keyProp.getValue(),
-                                                                       keyPropClass);
-                                                       map.put(key, mapValue);
-                                               }
-                                       }
-
-                                       // node
-                                       NodeIterator keysIt = childNode.getNodes();
-                                       while (keysIt.hasNext()) {
-                                               Node mapValueNode = keysIt.nextNode();
-                                               // FIXME: use property editor
-                                               Object key = mapValueNode.getName();
-
-                                               Object mapValue = nodeToBean(mapValueNode);
-
-                                               map.put(key, mapValue);
-                                       }
-                                       beanWrapper.setPropertyValue(name, map);
-                                       continue nodes;
-                               }
-
-                               // default
-                               Object value = nodeToBean(childNode);
-                               beanWrapper.setPropertyValue(name, value);
-
-                       }
-                       return beanWrapper.getWrappedInstance();
-               } catch (Exception e) {
-                       throw new ArgeoException("Cannot map node " + node, e);
-               }
-       }
-
-       /**
-        * Transforms an object to the specified jcr Node in order to persist it.
-        * 
-        * @param beanWrapper
-        * @param node
-        * @throws RepositoryException
-        */
-       protected void beanToNode(BeanWrapper beanWrapper, Node node)
-                       throws RepositoryException {
-               properties: for (PropertyDescriptor pd : beanWrapper
-                               .getPropertyDescriptors()) {
-                       String name = pd.getName();
-                       if (!beanWrapper.isReadableProperty(name))
-                               continue properties;// skip
-
-                       Object value = beanWrapper.getPropertyValue(name);
-                       if (value == null) {
-                               // remove values when updating
-                               if (node.hasProperty(name))
-                                       node.setProperty(name, (Value) null);
-                               if (node.hasNode(name))
-                                       node.getNode(name).remove();
-
-                               continue properties;
-                       }
-
-                       // if (uuidProperty != null && uuidProperty.equals(name)) {
-                       // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE);
-                       // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString());
-                       // continue properties;
-                       // }
-
-                       if ("class".equals(name)) {
-                               if (classProperty != null) {
-                                       node.setProperty(classProperty,
-                                                       ((Class<?>) value).getName());
-                                       // TODO: store a class hierarchy?
-                               }
-                               continue properties;
-                       }
-
-                       // Some bean reference other classes. We must deal with this case
-                       if (value instanceof Class<?>) {
-                               node.setProperty(name, ((Class<?>) value).getName());
-                               continue properties;
-                       }
-
-                       Value val = asValue(node.getSession(), value);
-                       if (val != null) {
-                               node.setProperty(name, val);
-                               continue properties;
-                       }
-
-                       if (value instanceof List<?>) {
-                               List<?> lst = (List<?>) value;
-                               addList(node, name, lst);
-                               continue properties;
-                       }
-
-                       if (value instanceof Map<?, ?>) {
-                               Map<?, ?> map = (Map<?, ?>) value;
-                               addMap(node, name, map);
-                               continue properties;
-                       }
-
-                       BeanWrapper child = createBeanWrapper(value);
-                       // TODO: delegate to another mapper
-
-                       // TODO: deal with references
-                       // Node childNode = findChildReference(session, child);
-                       // if (childNode != null) {
-                       // node.setProperty(name, childNode);
-                       // continue properties;
-                       // }
-
-                       // default case (recursive)
-                       if (node.hasNode(name)) {// update
-                               // TODO: optimize
-                               node.getNode(name).remove();
-                       }
-                       Node childNode = node.addNode(name);
-                       beanToNode(child, childNode);
-               }
-       }
-
-       /**
-        * Process specific case of list
-        * 
-        * @param node
-        * @param name
-        * @param lst
-        * @throws RepositoryException
-        */
-       protected void addList(Node node, String name, List<?> lst)
-                       throws RepositoryException {
-               if (node.hasNode(name)) {// update
-                       // TODO: optimize
-                       node.getNode(name).remove();
-               }
-
-               Node listNode = node.addNode(name);
-               listNode.setProperty(classProperty, lst.getClass().getName());
-               Value[] values = new Value[lst.size()];
-               boolean atLeastOneSet = false;
-               for (int i = 0; i < lst.size(); i++) {
-                       Object lstValue = lst.get(i);
-                       values[i] = asValue(node.getSession(), lstValue);
-                       if (values[i] != null) {
-                               atLeastOneSet = true;
-                       } else {
-                               Node childNode = findChildReference(node.getSession(),
-                                               createBeanWrapper(lstValue));
-                               if (childNode != null) {
-                                       values[i] = node.getSession().getValueFactory()
-                                                       .createValue(childNode);
-                                       atLeastOneSet = true;
-                               }
-                       }
-               }
-
-               // will be either properties or nodes, not both
-               if (!atLeastOneSet && lst.size() != 0) {
-                       for (Object lstValue : lst) {
-                               Node childNode = listNode.addNode(NODE_VALUE);
-                               beanToNode(createBeanWrapper(lstValue), childNode);
-                       }
-               } else {
-                       listNode.setProperty(name, values);
-               }
-       }
-
-       /**
-        * Process specific case of maps.
-        * 
-        * @param node
-        * @param name
-        * @param map
-        * @throws RepositoryException
-        */
-       protected void addMap(Node node, String name, Map<?, ?> map)
-                       throws RepositoryException {
-               if (node.hasNode(name)) {// update
-                       // TODO: optimize
-                       node.getNode(name).remove();
-               }
-
-               Node mapNode = node.addNode(name);
-               mapNode.setProperty(classProperty, map.getClass().getName());
-               for (Object key : map.keySet()) {
-                       Object mapValue = map.get(key);
-                       // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(),
-                       // null);
-                       String keyStr;
-                       // if (pe == null) {
-                       if (key instanceof CharSequence)
-                               keyStr = key.toString();
-                       else
-                               throw new ArgeoException(
-                                               "Cannot find property editor for class "
-                                                               + key.getClass());
-                       // } else {
-                       // pe.setValue(key);
-                       // keyStr = pe.getAsText();
-                       // }
-                       // TODO: check string format
-
-                       Value mapVal = asValue(node.getSession(), mapValue);
-                       if (mapVal != null)
-                               mapNode.setProperty(keyStr, mapVal);
-                       else {
-                               Node entryNode = mapNode.addNode(keyStr);
-                               beanToNode(createBeanWrapper(mapValue), entryNode);
-                       }
-
-               }
-
-       }
-
-       protected BeanWrapper createBeanWrapper(Object obj) {
-               return new BeanWrapperImpl(obj);
-       }
-
-       protected BeanWrapper createBeanWrapper(Class<?> clss) {
-               return new BeanWrapperImpl(clss);
-       }
-
-       /** Returns null if value cannot be found */
-       protected Value asValue(Session session, Object value)
-                       throws RepositoryException {
-               ValueFactory valueFactory = session.getValueFactory();
-               if (value instanceof Integer)
-                       return valueFactory.createValue((Integer) value);
-               else if (value instanceof Long)
-                       return valueFactory.createValue((Long) value);
-               else if (value instanceof Float)
-                       return valueFactory.createValue((Float) value);
-               else if (value instanceof Double)
-                       return valueFactory.createValue((Double) value);
-               else if (value instanceof Boolean)
-                       return valueFactory.createValue((Boolean) value);
-               else if (value instanceof Calendar)
-                       return valueFactory.createValue((Calendar) value);
-               else if (value instanceof Date) {
-                       Calendar cal = new GregorianCalendar();
-                       cal.setTime((Date) value);
-                       return valueFactory.createValue(cal);
-               } else if (value instanceof CharSequence)
-                       return valueFactory.createValue(value.toString());
-               else if (value instanceof InputStream) {
-                       Binary binary = session.getValueFactory().createBinary(
-                                       (InputStream) value);
-                       return valueFactory.createValue(binary);
-               } else
-                       return null;
-       }
-
-       protected Class<?> classFromProperty(Property property)
-                       throws RepositoryException {
-               switch (property.getType()) {
-               case PropertyType.LONG:
-                       return Long.class;
-               case PropertyType.DOUBLE:
-                       return Double.class;
-               case PropertyType.STRING:
-                       return String.class;
-               case PropertyType.BOOLEAN:
-                       return Boolean.class;
-               case PropertyType.DATE:
-                       return Calendar.class;
-               case PropertyType.NAME:
-                       return null;
-               default:
-                       throw new ArgeoException("Cannot find class for property "
-                                       + property + ", type="
-                                       + PropertyType.nameFromValue(property.getType()));
-               }
-       }
-
-       protected Object asObject(Value value, Class<?> propClass)
-                       throws RepositoryException {
-               if (propClass.equals(Integer.class))
-                       return (int) value.getLong();
-               else if (propClass.equals(Long.class))
-                       return value.getLong();
-               else if (propClass.equals(Float.class))
-                       return (float) value.getDouble();
-               else if (propClass.equals(Double.class))
-                       return value.getDouble();
-               else if (propClass.equals(Boolean.class))
-                       return value.getBoolean();
-               else if (CharSequence.class.isAssignableFrom(propClass))
-                       return value.getString();
-               else if (InputStream.class.isAssignableFrom(propClass))
-                       return value.getBinary().getStream();
-               else if (Calendar.class.isAssignableFrom(propClass))
-                       return value.getDate();
-               else if (Date.class.isAssignableFrom(propClass))
-                       return value.getDate().getTime();
-               else
-                       return null;
-       }
-
-       protected Node findChildReference(Session session, BeanWrapper child)
-                       throws RepositoryException {
-               if (child.isReadableProperty(uuidProperty)) {
-                       String childUuid = child.getPropertyValue(uuidProperty).toString();
-                       try {
-                               return session.getNodeByIdentifier(childUuid);
-                       } catch (ItemNotFoundException e) {
-                               if (strictUuidReference)
-                                       throw new ArgeoException("No node found with uuid "
-                                                       + childUuid, e);
-                       }
-               }
-               return null;
-       }
-
-       protected Class<?> loadClass(String name) {
-               // log.debug("Class loader: " + classLoader);
-               try {
-                       return classLoader.loadClass(name);
-               } catch (ClassNotFoundException e) {
-                       throw new ArgeoException("Cannot load class " + name, e);
-               }
-       }
-
-       protected String propertyName(String name) {
-               return name;
-       }
-
-       public void setVersioning(Boolean versioning) {
-               this.versioning = versioning;
-       }
-
-       public void setUuidProperty(String uuidProperty) {
-               this.uuidProperty = uuidProperty;
-       }
-
-       public void setClassProperty(String classProperty) {
-               this.classProperty = classProperty;
-       }
-
-       public void setStrictUuidReference(Boolean strictUuidReference) {
-               this.strictUuidReference = strictUuidReference;
-       }
-
-       public void setPrimaryNodeType(String primaryNodeType) {
-               this.primaryNodeType = primaryNodeType;
-       }
-
-       public void setClassLoader(ClassLoader classLoader) {
-               this.classLoader = classLoader;
-       }
-
-       public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider) {
-               this.nodeMapperProvider = nodeMapperProvider;
-       }
-
-       public String getPrimaryNodeType() {
-               return this.primaryNodeType;
-       }
-
-       public String getClassProperty() {
-               return this.classProperty;
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/spring/ThreadBoundSession.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/spring/ThreadBoundSession.java
deleted file mode 100644 (file)
index a46bef1..0000000
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.spring;
-
-import org.argeo.jcr.ThreadBoundJcrSessionFactory;
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.beans.factory.FactoryBean;
-import org.springframework.beans.factory.InitializingBean;
-
-public class ThreadBoundSession extends ThreadBoundJcrSessionFactory implements FactoryBean, InitializingBean, DisposableBean{
-       public void afterPropertiesSet() throws Exception {
-               init();
-       }
-
-       public void destroy() throws Exception {
-               dispose();
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/tabular/JcrTabularRowIterator.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/tabular/JcrTabularRowIterator.java
deleted file mode 100644 (file)
index ca0635c..0000000
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.tabular;
-
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.ArrayBlockingQueue;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.NodeIterator;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.util.CsvParser;
-import org.argeo.util.tabular.ArrayTabularRow;
-import org.argeo.util.tabular.TabularColumn;
-import org.argeo.util.tabular.TabularRow;
-import org.argeo.util.tabular.TabularRowIterator;
-
-/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */
-public class JcrTabularRowIterator implements TabularRowIterator {
-       private Boolean hasNext = null;
-       private Boolean parsingCompleted = false;
-
-       private Long currentRowNumber = 0l;
-
-       private List<TabularColumn> header = new ArrayList<TabularColumn>();
-
-       /** referenced so that we can close it */
-       private Binary binary;
-       private InputStream in;
-
-       private CsvParser csvParser;
-       private ArrayBlockingQueue<List<String>> textLines;
-
-       public JcrTabularRowIterator(Node tableNode) {
-               try {
-                       for (NodeIterator it = tableNode.getNodes(); it.hasNext();) {
-                               Node node = it.nextNode();
-                               if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) {
-                                       Integer type = PropertyType.valueFromName(node.getProperty(
-                                                       Property.JCR_REQUIRED_TYPE).getString());
-                                       TabularColumn tc = new TabularColumn(node.getProperty(
-                                                       Property.JCR_TITLE).getString(), type);
-                                       header.add(tc);
-                               }
-                       }
-                       Node contentNode = tableNode.getNode(Property.JCR_CONTENT);
-                       if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) {
-                               textLines = new ArrayBlockingQueue<List<String>>(1000);
-                               csvParser = new CsvParser() {
-                                       protected void processLine(Integer lineNumber,
-                                                       List<String> header, List<String> tokens) {
-                                               try {
-                                                       textLines.put(tokens);
-                                               } catch (InterruptedException e) {
-                                                       // TODO Auto-generated catch block
-                                                       e.printStackTrace();
-                                               }
-                                               // textLines.add(tokens);
-                                               if (hasNext == null) {
-                                                       hasNext = true;
-                                                       synchronized (JcrTabularRowIterator.this) {
-                                                               JcrTabularRowIterator.this.notifyAll();
-                                                       }
-                                               }
-                                       }
-                               };
-                               csvParser.setNoHeader(true);
-                               binary = contentNode.getProperty(Property.JCR_DATA).getBinary();
-                               in = binary.getStream();
-                               Thread thread = new Thread(contentNode.getPath() + " reader") {
-                                       public void run() {
-                                               try {
-                                                       csvParser.parse(in);
-                                               } finally {
-                                                       parsingCompleted = true;
-                                                       IOUtils.closeQuietly(in);
-                                               }
-                                       }
-                               };
-                               thread.start();
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot read table " + tableNode, e);
-               }
-       }
-
-       public synchronized boolean hasNext() {
-               // we don't know if there is anything available
-               // while (hasNext == null)
-               // try {
-               // wait();
-               // } catch (InterruptedException e) {
-               // // silent
-               // // FIXME better deal with interruption
-               // Thread.currentThread().interrupt();
-               // break;
-               // }
-
-               // buffer not empty
-               if (!textLines.isEmpty())
-                       return true;
-
-               // maybe the parsing is finished but the flag has not been set
-               while (!parsingCompleted && textLines.isEmpty())
-                       try {
-                               wait(100);
-                       } catch (InterruptedException e) {
-                               // silent
-                               // FIXME better deal with interruption
-                               Thread.currentThread().interrupt();
-                               break;
-                       }
-
-               // buffer not empty
-               if (!textLines.isEmpty())
-                       return true;
-
-               // (parsingCompleted && textLines.isEmpty())
-               return false;
-
-               // if (!hasNext && textLines.isEmpty()) {
-               // if (in != null) {
-               // IOUtils.closeQuietly(in);
-               // in = null;
-               // }
-               // if (binary != null) {
-               // JcrUtils.closeQuietly(binary);
-               // binary = null;
-               // }
-               // return false;
-               // } else
-               // return true;
-       }
-
-       public synchronized TabularRow next() {
-               try {
-                       List<String> tokens = textLines.take();
-                       List<Object> objs = new ArrayList<Object>(tokens.size());
-                       for (String token : tokens) {
-                               // TODO convert to other formats using header
-                               objs.add(token);
-                       }
-                       currentRowNumber++;
-                       return new ArrayTabularRow(objs);
-               } catch (InterruptedException e) {
-                       // silent
-                       // FIXME better deal with interruption
-               }
-               return null;
-       }
-
-       public void remove() {
-               throw new UnsupportedOperationException();
-       }
-
-       public Long getCurrentRowNumber() {
-               return currentRowNumber;
-       }
-
-       public List<TabularColumn> getHeader() {
-               return header;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/tabular/JcrTabularWriter.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/tabular/JcrTabularWriter.java
deleted file mode 100644 (file)
index 718ff23..0000000
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.tabular;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.util.List;
-
-import javax.jcr.Binary;
-import javax.jcr.Node;
-import javax.jcr.Property;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-
-import org.apache.commons.io.IOUtils;
-import org.argeo.ArgeoException;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.jcr.JcrUtils;
-import org.argeo.util.CsvWriter;
-import org.argeo.util.tabular.TabularColumn;
-import org.argeo.util.tabular.TabularWriter;
-
-/** Write / reference tabular content in a JCR repository. */
-public class JcrTabularWriter implements TabularWriter {
-       private Node contentNode;
-       private ByteArrayOutputStream out;
-       private CsvWriter csvWriter;
-       
-       @SuppressWarnings("unused")
-       private final List<TabularColumn> columns;
-
-       /** Creates a table node */
-       public JcrTabularWriter(Node tableNode, List<TabularColumn> columns,
-                       String contentNodeType) {
-               try {
-                       this.columns = columns;
-                       for (TabularColumn column : columns) {
-                               String normalized = JcrUtils.replaceInvalidChars(column
-                                               .getName());
-                               Node columnNode = tableNode.addNode(normalized,
-                                               ArgeoTypes.ARGEO_COLUMN);
-                               columnNode.setProperty(Property.JCR_TITLE, column.getName());
-                               if (column.getType() != null)
-                                       columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
-                                                       PropertyType.nameFromValue(column.getType()));
-                               else
-                                       columnNode.setProperty(Property.JCR_REQUIRED_TYPE,
-                                                       PropertyType.TYPENAME_STRING);
-                       }
-                       contentNode = tableNode.addNode(Property.JCR_CONTENT,
-                                       contentNodeType);
-                       if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) {
-                               contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv");
-                               contentNode.setProperty(Property.JCR_ENCODING, "UTF-8");
-                               out = new ByteArrayOutputStream();
-                               csvWriter = new CsvWriter(out);
-                       }
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot create table node " + tableNode, e);
-               }
-       }
-
-       public void appendRow(Object[] row) {
-               csvWriter.writeLine(row);
-       }
-
-       public void close() {
-               Binary binary = null;
-               InputStream in = null;
-               try {
-                       // TODO parallelize with pipes and writing from another thread
-                       in = new ByteArrayInputStream(out.toByteArray());
-                       binary = contentNode.getSession().getValueFactory()
-                                       .createBinary(in);
-                       contentNode.setProperty(Property.JCR_DATA, binary);
-               } catch (RepositoryException e) {
-                       throw new ArgeoException("Cannot store data in " + contentNode, e);
-               } finally {
-                       IOUtils.closeQuietly(in);
-                       JcrUtils.closeQuietly(binary);
-               }
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/java/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.server.jcr/src/main/java/org/argeo/jcr/unit/AbstractJcrTestCase.java
deleted file mode 100644 (file)
index 530605e..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.unit;
-
-import java.io.File;
-
-import javax.jcr.Repository;
-import javax.jcr.Session;
-import javax.jcr.SimpleCredentials;
-
-import junit.framework.TestCase;
-
-import org.apache.commons.io.FileUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.ArgeoException;
-
-public abstract class AbstractJcrTestCase extends TestCase {
-       private final static Log log = LogFactory.getLog(AbstractJcrTestCase.class);
-
-       private Repository repository;
-       private Session session = null;
-
-       protected abstract File getRepositoryFile() throws Exception;
-
-       protected abstract Repository createRepository() throws Exception;
-
-       @Override
-       protected void setUp() throws Exception {
-               File homeDir = getHomeDir();
-               FileUtils.deleteDirectory(homeDir);
-               repository = createRepository();
-       }
-
-       protected File getHomeDir() {
-               File homeDir = new File(System.getProperty("java.io.tmpdir"),
-                               AbstractJcrTestCase.class.getSimpleName() + "-"
-                                               + System.getProperty("user.name"));
-               return homeDir;
-       }
-
-       @Override
-       protected void tearDown() throws Exception {
-               if (session != null) {
-                       session.logout();
-                       if (log.isDebugEnabled())
-                               log.debug("Logout session");
-               }
-       }
-
-       protected Session session() {
-               if (session == null) {
-                       try {
-                               if (log.isDebugEnabled())
-                                       log.debug("Login session");
-                               session = getRepository().login(
-                                               new SimpleCredentials("demo", "demo".toCharArray()));
-                       } catch (Exception e) {
-                               throw new ArgeoException("Cannot login to repository", e);
-                       }
-               }
-               return session;
-       }
-
-       protected Repository getRepository() {
-               return repository;
-       }
-
-       /**
-        * enables children class to set an existing repository in case it is not
-        * deleted on startup, to test migration by instance
-        */
-       protected void setRepository(Repository repository) {
-               this.repository = repository;
-       }
-}
diff --git a/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd b/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd
deleted file mode 100644 (file)
index fbfea9d..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<argeo = 'http://www.argeo.org/ns/argeo'>
-
-// GENERIC TYPES NOT AVAILABLE IN JCR
-[argeo:link] > mix:created, mix:lastModified
-mixin
-// URI(s)
-- argeo:uri (STRING) m
-
-[argeo:references] > nt:unstructured
-- * (REFERENCE) *
-
-// DATA MODEL
-[argeo:dataModel] > mix:created, mix:lastModified, mix:versionable
-mixin
-- argeo:uri (STRING) m
-- argeo:dataModelVersion (STRING) m
-
-// USER NODES
-// user should be lower case, between 3 and 15 characters long
-[argeo:userHome] > mix:created, mix:lastModified
-mixin
-- argeo:userID (STRING) m
-- argeo:remoteRoles (STRING) *
-// deprecated. for backward compatibility:
-+ argeo:profile (argeo:userProfile)
-+ argeo:keyring (argeo:pbeSpec)
-+ argeo:preferences (argeo:preferenceNode)
-
-[argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable
-mixin
-- argeo:userID (STRING) m
-- argeo:enabled (BOOLEAN)
-- argeo:accountNonExpired (BOOLEAN)
-- argeo:accountNonLocked (BOOLEAN)
-- argeo:credentialsNonExpired (BOOLEAN)
-
-[argeo:preferenceNode] > mix:lastModified, mix:versionable
-mixin
-+ * (argeo:preferenceNode) * version
-
-[argeo:remoteRepository] > nt:unstructured
-- argeo:uri (STRING)
-- argeo:userID (STRING)
-+ argeo:password (argeo:encrypted)
-
-// TABULAR CONTENT
-[argeo:table] > nt:file
-+ * (argeo:column) *
-
-[argeo:column] > mix:title
-- jcr:requiredType (STRING) = 'STRING'
-
-[argeo:csv] > nt:resource
-
-// CRYPTO
-[argeo:encrypted] > nt:base
-mixin
-// initialization vector used by some algorithms
-- argeo:iv (BINARY)
-
-[argeo:pbeKeySpec] > nt:base
-mixin
-- argeo:secretKeyFactory (STRING)
-- argeo:salt (BINARY)
-- argeo:iterationCount (LONG)
-- argeo:keyLength (LONG)
-- argeo:secretKeyEncryption (STRING)
-
-[argeo:pbeSpec] > argeo:pbeKeySpec
-mixin
-- argeo:cipher (STRING)
-
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java b/org.argeo.server.jcr/src/test/java/org/argeo/jcr/AbstractInternalJackrabbitTestCase.java
deleted file mode 100644 (file)
index 23281d0..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.io.File;
-
-import javax.jcr.Repository;
-
-import org.apache.jackrabbit.core.TransientRepository;
-import org.argeo.jcr.unit.AbstractJcrTestCase;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-
-/** Factorizes configuration of an in memory transient repository */
-public abstract class AbstractInternalJackrabbitTestCase extends
-               AbstractJcrTestCase {
-       protected File getRepositoryFile() throws Exception {
-               Resource res = new ClassPathResource(
-                               "org/argeo/server/jcr/repository-memory.xml");
-               return res.getFile();
-       }
-
-       protected Repository createRepository() throws Exception {
-               Repository repository = new TransientRepository(getRepositoryFile(),
-                               getHomeDir());
-               return repository;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/jcr/CollectionsObject.java b/org.argeo.server.jcr/src/test/java/org/argeo/jcr/CollectionsObject.java
deleted file mode 100644 (file)
index 1cbb931..0000000
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class CollectionsObject {
-       private String id;
-       private String label;
-       private SimpleObject simpleObject;
-       private List<String> stringList = new ArrayList<String>();
-       private Map<String, Float> floatMap = new HashMap<String, Float>();
-       private Map<SimpleObject, String> objectMap = new HashMap<SimpleObject, String>();
-       private Map<String, Map<String, String>> mapOfMaps = new HashMap<String, Map<String, String>>();
-
-       public String getId() {
-               return id;
-       }
-
-       public void setId(String id) {
-               this.id = id;
-       }
-
-       public String getLabel() {
-               return label;
-       }
-
-       public void setLabel(String label) {
-               this.label = label;
-       }
-
-       public SimpleObject getSimpleObject() {
-               return simpleObject;
-       }
-
-       public void setSimpleObject(SimpleObject simpleObject) {
-               this.simpleObject = simpleObject;
-       }
-
-       public List<String> getStringList() {
-               return stringList;
-       }
-
-       public void setStringList(List<String> stringList) {
-               this.stringList = stringList;
-       }
-
-       public Map<String, Float> getFloatMap() {
-               return floatMap;
-       }
-
-       public void setFloatMap(Map<String, Float> floatMap) {
-               this.floatMap = floatMap;
-       }
-
-       public Map<SimpleObject, String> getObjectMap() {
-               return objectMap;
-       }
-
-       public void setObjectMap(Map<SimpleObject, String> objectMap) {
-               this.objectMap = objectMap;
-       }
-
-       public Map<String, Map<String, String>> getMapOfMaps() {
-               return mapOfMaps;
-       }
-
-       public void setMapOfMaps(Map<String, Map<String, String>> mapOfMaps) {
-               this.mapOfMaps = mapOfMaps;
-       }
-}
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java b/org.argeo.server.jcr/src/test/java/org/argeo/jcr/MapperTest.java
deleted file mode 100644 (file)
index 5486ff4..0000000
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import javax.jcr.Node;
-
-import org.argeo.jcr.spring.BeanNodeMapper;
-
-public class MapperTest extends AbstractInternalJackrabbitTestCase {
-       public void testSimpleObject() throws Exception {
-               SimpleObject mySo = new SimpleObject();
-               mySo.setInteger(100);
-               mySo.setString("hello world");
-
-               OtherObject oo1 = new OtherObject();
-               oo1.setKey("someKey");
-               oo1.setValue("stringValue");
-               mySo.setOtherObject(oo1);
-
-               OtherObject oo2 = new OtherObject();
-               oo2.setKey("anotherSimpleObject");
-               oo2.setValue(new SimpleObject());
-               mySo.setAnotherObject(oo2);
-
-               BeanNodeMapper bnm = new BeanNodeMapper();
-
-               Node node = bnm.save(session(), mySo);
-               session().save();
-               JcrUtils.debug(node);
-       }
-}
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/jcr/OtherObject.java b/org.argeo.server.jcr/src/test/java/org/argeo/jcr/OtherObject.java
deleted file mode 100644 (file)
index 5cce8ff..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-public class OtherObject {
-       private String key;
-       private Object value;
-
-       public String getKey() {
-               return key;
-       }
-
-       public void setKey(String key) {
-               this.key = key;
-       }
-
-       public Object getValue() {
-               return value;
-       }
-
-       public void setValue(Object value) {
-               this.value = value;
-       }
-}
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/jcr/SimpleObject.java b/org.argeo.server.jcr/src/test/java/org/argeo/jcr/SimpleObject.java
deleted file mode 100644 (file)
index cdc32fc..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr;
-
-import java.util.UUID;
-
-public class SimpleObject {
-       private String string;
-       private String uuid = UUID.randomUUID().toString();
-       private Integer integer;
-       private OtherObject otherObject;
-       private OtherObject anotherObject;
-
-       public String getString() {
-               return string;
-       }
-
-       public void setString(String sting) {
-               this.string = sting;
-       }
-
-       public Integer getInteger() {
-               return integer;
-       }
-
-       public void setInteger(Integer integer) {
-               this.integer = integer;
-       }
-
-       public OtherObject getOtherObject() {
-               return otherObject;
-       }
-
-       public void setOtherObject(OtherObject otherObject) {
-               this.otherObject = otherObject;
-       }
-
-       public OtherObject getAnotherObject() {
-               return anotherObject;
-       }
-
-       public void setAnotherObject(OtherObject anotherObject) {
-               this.anotherObject = anotherObject;
-       }
-
-       @Override
-       public boolean equals(Object obj) {
-               return string.equals(((SimpleObject) obj).string);
-       }
-
-       @Override
-       public int hashCode() {
-               return string.hashCode();
-       }
-
-       public void setUuid(String uuid) {
-               this.uuid = uuid;
-       }
-
-       public String getUuid() {
-               return uuid;
-       }
-
-}
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java b/org.argeo.server.jcr/src/test/java/org/argeo/jcr/tabular/JcrTabularTest.java
deleted file mode 100644 (file)
index 2045f9a..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.jcr.tabular;
-
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.jcr.Node;
-import javax.jcr.PropertyType;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.apache.jackrabbit.commons.cnd.CndImporter;
-import org.argeo.jcr.AbstractInternalJackrabbitTestCase;
-import org.argeo.jcr.ArgeoNames;
-import org.argeo.jcr.ArgeoTypes;
-import org.argeo.util.tabular.TabularColumn;
-import org.argeo.util.tabular.TabularRow;
-import org.argeo.util.tabular.TabularRowIterator;
-import org.argeo.util.tabular.TabularWriter;
-
-public class JcrTabularTest extends AbstractInternalJackrabbitTestCase {
-       private final static Log log = LogFactory.getLog(JcrTabularTest.class);
-
-       public void testWriteReadCsv() throws Exception {
-               session().setNamespacePrefix("argeo", ArgeoNames.ARGEO_NAMESPACE);
-               InputStreamReader reader = new InputStreamReader(getClass()
-                               .getResourceAsStream("/org/argeo/jcr/argeo.cnd"));
-               CndImporter.registerNodeTypes(reader, session());
-               reader.close();
-
-               // write
-               Integer columnCount = 15;
-               Long rowCount = 1000l;
-               String stringValue = "test, \ntest";
-
-               List<TabularColumn> header = new ArrayList<TabularColumn>();
-               for (int i = 0; i < columnCount; i++) {
-                       header.add(new TabularColumn("col" + i, PropertyType.STRING));
-               }
-               Node tableNode = session().getRootNode().addNode("table",
-                               ArgeoTypes.ARGEO_TABLE);
-               TabularWriter writer = new JcrTabularWriter(tableNode, header,
-                               ArgeoTypes.ARGEO_CSV);
-               for (int i = 0; i < rowCount; i++) {
-                       List<Object> objs = new ArrayList<Object>();
-                       for (int j = 0; j < columnCount; j++) {
-                               objs.add(stringValue);
-                       }
-                       writer.appendRow(objs.toArray());
-               }
-               writer.close();
-               session().save();
-
-               if (log.isDebugEnabled())
-                       log.debug("Wrote tabular content " + rowCount + " rows, "
-                                       + columnCount + " columns");
-               // read
-               TabularRowIterator rowIt = new JcrTabularRowIterator(tableNode);
-               Long count = 0l;
-               while (rowIt.hasNext()) {
-                       TabularRow tr = rowIt.next();
-                       assertEquals(header.size(), tr.size());
-                       count++;
-               }
-               assertEquals(rowCount, count);
-               if (log.isDebugEnabled())
-                       log.debug("Read tabular content " + rowCount + " rows, "
-                                       + columnCount + " columns");
-       }
-}
diff --git a/org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java b/org.argeo.server.jcr/src/test/java/org/argeo/server/jcr/JcrResourceAdapterTest.java
deleted file mode 100644 (file)
index b691572..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2007-2012 Argeo GmbH
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *         http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.argeo.server.jcr;
-
-import java.io.InputStream;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.List;
-
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.argeo.jcr.AbstractInternalJackrabbitTestCase;
-import org.argeo.jcr.JcrResourceAdapter;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.Resource;
-
-public class JcrResourceAdapterTest extends AbstractInternalJackrabbitTestCase {
-       private static SimpleDateFormat sdf = new SimpleDateFormat(
-                       "yyyyMMdd:hhmmss.SSS");
-
-       private final static Log log = LogFactory
-                       .getLog(JcrResourceAdapterTest.class);
-
-       private JcrResourceAdapter jra;
-
-       public void testCreate() throws Exception {
-               String basePath = "/test/subdir";
-               jra.mkdirs(basePath);
-               Resource res = new ClassPathResource("org/argeo/server/jcr/dummy00.xls");
-               String filePath = basePath + "/dummy.xml";
-               jra.create(filePath, res.getInputStream(), "application/vnd.ms-excel");
-               InputStream in = jra.retrieve(filePath);
-               assertTrue(IOUtils.contentEquals(res.getInputStream(), in));
-       }
-
-       public void testVersioning() throws Exception {
-               String basePath = "/test/versions";
-               jra.mkdirs(basePath);
-               String filePath = basePath + "/dummy.xml";
-               Resource res00 = new ClassPathResource(
-                               "org/argeo/server/jcr/dummy00.xls");
-               jra.create(filePath, res00.getInputStream(), "application/vnd.ms-excel");
-               Resource res01 = new ClassPathResource(
-                               "org/argeo/server/jcr/dummy01.xls");
-               jra.update(filePath, res01.getInputStream());
-               Resource res02 = new ClassPathResource(
-                               "org/argeo/server/jcr/dummy02.xls");
-               jra.update(filePath, res02.getInputStream());
-
-               List<Calendar> versions = jra.listVersions(filePath);
-               log.debug("Versions of " + filePath);
-               int count = 0;
-               for (Calendar version : versions) {
-                       log.debug(" " + (count == 0 ? "base" : count - 1) + "\t"
-                                       + sdf.format(version.getTime()));
-                       count++;
-               }
-
-               assertEquals(4, versions.size());
-
-               InputStream in = jra.retrieve(filePath, 1);
-               assertTrue(IOUtils.contentEquals(res01.getInputStream(), in));
-               in = jra.retrieve(filePath, 0);
-               assertTrue(IOUtils.contentEquals(res00.getInputStream(), in));
-               in = jra.retrieve(filePath, 2);
-               assertTrue(IOUtils.contentEquals(res02.getInputStream(), in));
-               Resource res03 = new ClassPathResource(
-                               "org/argeo/server/jcr/dummy03.xls");
-               jra.update(filePath, res03.getInputStream());
-               in = jra.retrieve(filePath, 1);
-               assertTrue(IOUtils.contentEquals(res01.getInputStream(), in));
-       }
-
-       @Override
-       protected void setUp() throws Exception {
-               log.debug("SET UP");
-               super.setUp();
-               jra = new JcrResourceAdapter();
-               jra.setSession(session());
-       }
-
-       @Override
-       protected void tearDown() throws Exception {
-               log.debug("TEAR DOWN");
-               super.tearDown();
-       }
-}
diff --git a/org.argeo.server.jcr/src/test/resources/log4j.properties b/org.argeo.server.jcr/src/test/resources/log4j.properties
deleted file mode 100644 (file)
index ca995af..0000000
+++ /dev/null
@@ -1,13 +0,0 @@
-log4j.rootLogger=WARN, console
-
-## Levels
-log4j.logger.org.argeo=DEBUG
-log4j.logger.org.apache.jackrabbit=OFF
-
-## Appenders
-# console is set to be a ConsoleAppender.
-log4j.appender.console=org.apache.log4j.ConsoleAppender
-
-# console uses PatternLayout.
-log4j.appender.console.layout=org.apache.log4j.PatternLayout
-log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c%n
diff --git a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy00.xls b/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy00.xls
deleted file mode 100644 (file)
index e5846fe..0000000
Binary files a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy00.xls and /dev/null differ
diff --git a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy01.xls b/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy01.xls
deleted file mode 100644 (file)
index b5c6b55..0000000
Binary files a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy01.xls and /dev/null differ
diff --git a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy02.xls b/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy02.xls
deleted file mode 100644 (file)
index d73bc66..0000000
Binary files a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy02.xls and /dev/null differ
diff --git a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy03.xls b/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy03.xls
deleted file mode 100644 (file)
index 0759cb9..0000000
Binary files a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/dummy03.xls and /dev/null differ
diff --git a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-h2.xml b/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-h2.xml
deleted file mode 100644 (file)
index ef3f0c4..0000000
+++ /dev/null
@@ -1,98 +0,0 @@
-<?xml version="1.0"?>
-<!--
-
-    Copyright (C) 2007-2012 Argeo GmbH
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-            http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
-                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
-       <!-- Shared datasource -->
-       <DataSources>
-               <DataSource name="dataSource">
-                       <param name="driver" value="org.h2.Driver" />
-                       <param name="url" value="jdbc:h2:mem:jackrabbit" />
-                       <param name="user" value="sa" />
-                       <param name="password" value="" />
-                       <param name="databaseType" value="h2" />
-                       <param name="maxPoolSize" value="10" />
-               </DataSource>
-       </DataSources>
-
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schema" value="default" />
-               <param name="schemaObjectPrefix" value="fs_" />
-       </FileSystem>
-       <DataStore class="org.apache.jackrabbit.core.data.db.DbDataStore">
-               <param name="dataSourceName" value="dataSource" />
-               <param name="schemaObjectPrefix" value="ds_" />
-       </DataStore>
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="dev" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_fs_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="${wsp.name}_pm_" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${wsp.home}/index" />
-               </SearchIndex>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.db.DbFileSystem">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schema" value="default" />
-                       <param name="schemaObjectPrefix" value="fs_ver_" />
-               </FileSystem>
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.pool.H2PersistenceManager">
-                       <param name="dataSourceName" value="dataSource" />
-                       <param name="schemaObjectPrefix" value="pm_ver_" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/repository/index" />
-               <param name="extractorPoolSize" value="2" />
-               <param name="supportHighlighting" value="true" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
-                       workspaceName="security" />
-               <AccessManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
-               <LoginModule
-                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
-                       <param name="anonymousId" value="anonymous" />
-                       <param name="adminId" value="admin" />
-               </LoginModule>
-       </Security>
-</Repository>
\ No newline at end of file
diff --git a/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml b/org.argeo.server.jcr/src/test/resources/org/argeo/server/jcr/repository-memory.xml
deleted file mode 100644 (file)
index 8395424..0000000
+++ /dev/null
@@ -1,72 +0,0 @@
-<?xml version="1.0"?>
-<!--
-
-    Copyright (C) 2007-2012 Argeo GmbH
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-            http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-
--->
-<!DOCTYPE Repository PUBLIC "-//The Apache Software Foundation//DTD Jackrabbit 1.6//EN"
-                            "http://jackrabbit.apache.org/dtd/repository-2.0.dtd">
-<Repository>
-       <!-- File system and datastore -->
-       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-
-       <!-- Workspace templates -->
-       <Workspaces rootPath="${rep.home}/workspaces"
-               defaultWorkspace="main" configRootPath="/workspaces" />
-       <Workspace name="${wsp.name}">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-               </PersistenceManager>
-               <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-                       <param name="path" value="${rep.home}/repository/index" />
-                       <param name="directoryManagerClass"
-                               value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-                       <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               </SearchIndex>
-       </Workspace>
-
-       <!-- Versioning -->
-       <Versioning rootPath="${rep.home}/version">
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-               <PersistenceManager
-                       class="org.apache.jackrabbit.core.persistence.bundle.BundleFsPersistenceManager">
-                       <param name="blobFSBlockSize" value="1" />
-               </PersistenceManager>
-       </Versioning>
-
-       <!-- Indexing -->
-       <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
-               <param name="path" value="${rep.home}/repository/index" />
-               <param name="directoryManagerClass"
-                       value="org.apache.jackrabbit.core.query.lucene.directory.RAMDirectoryManager" />
-               <FileSystem class="org.apache.jackrabbit.core.fs.mem.MemoryFileSystem" />
-       </SearchIndex>
-
-       <!-- Security -->
-       <Security appName="Jackrabbit">
-               <SecurityManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleSecurityManager"
-                       workspaceName="security" />
-               <AccessManager
-                       class="org.apache.jackrabbit.core.security.simple.SimpleAccessManager" />
-               <LoginModule
-                       class="org.apache.jackrabbit.core.security.simple.SimpleLoginModule">
-                       <param name="anonymousId" value="anonymous" />
-                       <param name="adminId" value="admin" />
-               </LoginModule>
-       </Security>
-</Repository>
\ No newline at end of file